
                      ЧАСТЬ I. ПРОГРАММИРОВАНИЕ И КОДИРОВАНИЕ

             Глава 1. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ 1: ИНСТРУМЕНТАЛЬНЫЕ
                      СРЕДСТВА СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ            1-1

         Потребность в короткой записи операторов 1-1
         Введение в МАКРОСЫ 1-2
              Метки типа LOCAL 1-4
              Директивы листинга макро 1-9
              Макробиблиотеки 1-9
              Макродиректива повторения - REPT 1-10
              Более подробно о макродирективах повторения - IRP и IRPC 1-12
              Резюме по использованию макро 1-13
         Условное  ассемблирование 1-13
              Операторы отношений 1-19
              Выводы 1-21
         Условное ассемблирование и МАКРОСЫ 1-21
              Определение типов операндов 1-22
              Фазовые ошибки и некоторые особенности MASM 1-23
              Сравнение строк. Пример 1-24
              Синтаксический анализ аргументов макро 1-26
              Предупреждения по использованию в MASM условного
              ассемблирования и макросов 1-29
         Структурированные операторы управления в языке Ассемблер 1-31
              Как работают структурированные макросы 1-38
              Приемы кодирования и некоторые предупреждения 1-40
              Макро псевдо-CASE 1-43
         Макросы данных 1-44
         Макросы генерации программного кода 1-50
              Условные макросы 1-51
              Вложенные макросы 1-52
              Несколько слов о возможностях макро 1-53
              Макро, вызывающее подпрограммы 1-54
         Применение директивы STRUC 1-56
              Адресация к данным во множественных структурах 1-57
              Структуры как параметры подпрограмм 1-59
         Заключение 1-60



              Глава 2. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ 2: ПРОЕКТИРОВАНИЕ
                          И РЕАЛИЗАЦИЯ МОДУЛЬНЫХ ПРОГРАММ              2-1


         Принципы модульного программирования 2-1
              Опции проектирования 2-2
              Функциональная декомпозиция 2-2
              Минимизации количества передаваемых параметров 2-3
              Минимизации количества необходимых вызовов 2-3
              Правила модульного программирования 2-4
              Справочная литература 2-5
         Реализация модульных программ на языке Ассемблер 2-5
              Определение параметра, аргумента, переменной константы 2-6
              Параметры и модули 2-7
              Опции передачи параметров 2-7
              Передача через регистры 2-7
              Передача данных через общую область памяти 2-8
              Передача данных через память программы 2-9
              Передача данных в стек 2-9
              Краткое изложение опций передачи параметров 2-15
              Передача параметров по значению или адресу 2-16
              Передача по значению 2-16
              Передача по адресу 2-17
              Защита целостности передаваемых данных 2-17
              Функции в сравнении с подпрограммами 2-18
              Возврат значений в регистрах 2-18
              Возврат значений в общей области 2-18
              Возврат значений в стеке 2-19
              Отчеты об исключительных ситуациях 2-19
         Типы кодирования 2-20
              Размещение программного кода в памяти 2-21
              Относительное размещение 2-21
              Адресация относительно текущего сегмента 2-21
              Абсолютная адресация 2-22
              Типы программного кода 2-22
              Переместимый код 2-23
              Отдельные области данных 2-24
              Рекурсивные программы 2-25
              Повторно-входимый код - необходимое условие
              локальной памяти 2-25
              Локальная память в стеке 2-26
              Инструкции ENTER и LEAVE для локальной памяти стека 2-27
              Краткое изложение размещения программного кода 2-31
         Интерфейс с языками высокого уровня 2-32
              Модель сегмента фирмы "Микрософт" 2-36
         Назначение  и использование локального ЗУ в памяти 2-37
              Введение в управление памятью в MS-DOS 2-39
              Распределение памяти из языков высокого уровня 2-42
              Защита данных и управление областью действия данных 2-42
              Локальная память в сравнении с глобальной памятью 2-43
              Использование регистров сегмента 2-43
              Управление размером доступных данных 2-44
              Защита целостности данных 2-44
         Заключение 2-45


                     Глава 3. УПРАВЛЕНИЕ ПРОГРАММАМИ И ПАМЯТЬЮ         3-1

         Память MS-DOS 3-1
              Формат физической памяти MS-DOS 3-1
              Расширяемая и расширенная память 3-1
              Использование памяти MS-DOS 3-3
              Цепочки памяти MS-DOS 3-5
              Блок операционной среды программы 3-12
         Процессы MS-DOS 3-13
              Контекст процесса MS-DOS 3-13
         Сегмент программного префикса 3-13
              Адреса завершения PSP 3-14
              Таблица описателей файлов PSP 3-15
         SHOWMEM и указатель адреса среды PSP 3-21
              Функции для манипулирования PSP 3-22
              Файлы процессов MS-DOS: .EXE в сравнении с .COM 3-23
              Загрузка файла типа .COM 3-25
              Формат программного файла типа .EXE 3-25
              Блок начального распределения памяти .EXE 3-27
              Загрузчик процесса .EXE MS-DOS 3-29
              Перекрытия 3-30
         Резидентные программы 3-31
              Описание библиотеки исполняющей системы 3-31
              Загрузка резидентных подпрограмм из командной строки 3-32
              Доступ к резидентным подпрограммам через прерывания 3-34
              Как определить, установлены ли резидентные программы? 3-41
              Удаление резидентных подпрограмм из памяти 3-42
         Функция 4Bh - загрузка и выполнение программ 3-44
              Загрузка и выполнение программ через MS-DOS
              (код 4Bh с AL=0) 3-48
              Наследство и управление порожденной программой 3-49
              Выполнение команд MS-DOS с функцией 4Bh 3-50
              Важное предупреждение 3-50
              Загрузка программного оверлея (перекрытия)
              посредством MS-DOS (код функции 4Bh с AL = 3) 3-51
              Доступ к программному оверлею из порождающей программы 3-52
              Загрузка резидентных программ 3-55
              Специальный случай: библиотеки исполняющей системы (RTL)
              с неполным временем работы 3-55
         Переключение контекста и переключение стека 3-57
              Дополнительные соображения по переключению стеков 3-59
         Введение в резидентную часть оперативной памяти 3-60
              ROM-BIOS в сравнении с загружаемой BIOS 3-60
              Прерывания в сравнении с системами с опросом 3-61
              Внесение "заплат" в векторы прерываний 3-62
         REMOVE - пример интегрированной программы 3-65
         Заключение 3-72



             Глава 4. ПРОГРАММЫ TSR (ЗАВЕРШИТЬ И ОСТАВИТЬ РЕЗИДЕНТНОЙ)  4-1

         Обзор 4-1
         Работа с аппаратурой PC 4-2
              Аппаратные прерывания 4-3
              Программные прерывания 4-4
              Прерывания от таймера 4-4
              Клавиатура 4-4
              Аппаратура отображения 4-6
              МDA и CGA 4-6
              Занесение в память дисплея 4-7
              Видео поддержка ROM-BIOS 4-8
              Подмена прерывания 4-8
              Создание горячего ключа 4-9
              Подмена Int 16h 4-10
              Опрос буфера клавиатуры прерыванием
              от таймера Int 1Ch 4-11
              Ловушка для Int 9 4-12
              Управление состоянием клавиатуры 4-12
              Альтернатива для перехвата Int 1Сh 4-14
              Управление отображением на экране 4-15
         Работа в среде DOS 4-17
              Структуры данных ввода/вывода DOS 4-17
              "Список списков" 4-17
              Системная таблица файлов 4-18
              Сегмент программного префикса (PSP) 4-20
              Рабочая таблица файлов (JFT) 4-21
              Диспетчер BIOS, Int 21h 4-23
              Подпрограммы в/в символов 4-24
              Глобальные переменные DOS 4-25
         Обработка break 4-25
              Обработка критической ошибки 4-26
              Загрузка программы 4-27
              Завершение программы 4-28
         Загрузка и инициализация TSR 4-29
              Проверка версии используемой DOS 4-30
              Размещение резидентных копий TSR 4-30
              Запись адреса сегмента программного префикса (PSP) 4-34
              Запись адреса критической секции (INDOS)
              и адреса критической ошибки 4-34
              Захват векторов прерываний 4-35
              Проверка типа дисплея 4-37
              Освобождение операционной среды 4-37
              Завершение программы 4-38
         Реактивация, архитектура DOS и сервис 4-39
              Определение безопасности повторной активации 4-39
              Переключение стека и сохранение регистров 4-41
              Организация "ловушек" break  и критических ошибок 4-41
              Обращение к глобальным переменным 4-43
         Фоновая обработка с использованием Int 28h 4-43
         Удаление из памяти программ TSR 4-48
         Заключение 4-48

                Глава 5. ПРОГРАММЫ РЕАЛЬНОГО ВРЕМЕНИ В СРЕДЕ MS-DOS 5-1

         Обзор программ реального времени 5-1
              Что такое реальное время? 5-1
              Характеристики систем реального времени 5-2
              Основные типы систем реального времени 5-3
              Однонаправленные системы 5-3
              Двухнаправленные стабильные системы 5-4
              Двухнаправленные потенциально нестабильные системы 5-4
              Типичные временные характеристики и решения
              систем реального времени 5-5
         Использование MS-DOS для приложений реального времени 5-6
              Быстродействие MS-DOS 5-6
              Тактовая частота MS-DOS 5-6
              Передача данных в операционной системе MS-DOS 5-8
              Передача данных методом прямого доступа к памяти (DMA) 5-14
              Передача данных методом прерываний данных 5-14
              Сравнение методов передачи данных 5-15
              Средства ускоренной записи программ 5-16
              Случаи, когда следует использовать операционную систему
              MS DOS для прикладных программ реального времени 5-18
         Проектирование систем реального времени в MS-DOS 5-19
              Пример (упрощенной системы управления домашним
              хозяйством) 5-20
              Система упорядоченного опроса 5-24
              Основной цикл с прерываниями 5-25
              Циклические планировщики 5-27
              Выбор метода построения системы 5-30
         Многозадачность в MS-DOS 5-30
              Условия существования мультизадачного режима в
              персональном компьютере IBM PC/AT 5-31
         Заключение 5-31



                               ЧАСТЬ II. УСТРОЙСТВА

                   Глава 6.  УСТАНАВЛИВАЕМЫЕ  ДРАЙВЕРЫ УСТРОЙСТВ 6-1

         Зачем нужны драйверы устройств? 6-2
              Когда использовать драйверы устройств? 6-3
              MS-DOS - нереентерабельная система 6-3
         Установка драйверов устройств 6-4
              Файл CONFIG.SYS 6-5
              Использование команды ASSIGN для замены
              драйверов дисковых устройств 6-9
              Типы драйверов устройств 6-9
         Работа с драйвером в среде MS-DOS 6-10
              Функции CP/M-стиля для работы с символьными устройствами 6-11
              Работа с устройством с использованием блоков управления
              файлами 6-11
              Работа с устройствами на основе описателей файлов 6-11
              Функция 44H - управление вводом/выводом для устройств
              (IOCTL) 6-12
              Конфигурация с помощью команд управления вводом/выводом 6-13
              Группа команд управления вводом/выводом 6-15
              Прямой доступ к диску через прерывания INT 25H и INT 25H 6-15
              Опция "Ввод/вывод с проверкой" 6-16
              Выводы 6-17
         Создание драйверов устройств 6-17
              Заголовок драйвера 6-19
              Поле связи 6-19
              Слово атрибутов 6-19
              Вектора точек входа программ СТРАТЕГИЙ и ПРЕРЫВАНИЙ 6-22
              Поле имени/количества устройств 6-23
              Программа ПРЕРЫВАНИЙ 6-24
              Команды драйверов устройств 6-29
              Получение блока параметров BIOS 6-35
              Создание загрузочного файла драйвера устройства 6-46
              Отладка драйверов устройств 6-47
              Отображение списка загруженных в системе драйверов 6-48
         Пример драйвера виртуального диска 6-53
         Заключение 6-68



                     Глава 7. ИСПОЛЬЗОВАНИЕ РАСШИРЕННОЙ ПАМЯТИ        7-1

         Урок истории 7-3
              LIM EMS 7-4
              LIM EMS  3.2 7-5
              Идеи и терминология LIM EMS 3.2 7-5
              Улучшенная спецификация расширенной памяти 7-6
              Ограничение размера окна  7-6
              LIM EMS 4.0 7-7
              LIM EMS 4.0 по сравнению с LIM EMS 3.2 и AQA EEMS 7-9
              Соображения по совместимости 7-9
              Технические соображения 7-11
         Менеджер расширенной памяти 7-11
              Функции менеджера расширенной памяти 7-11
              Реализации менеджера расширенной памяти 7-20
              Оборудование и  программное  обеспечение  расширенной
              памяти 7-21
              Аппаратура и программное обеспечение 80386 7-21
              Только программное обеспечение 7-22
              Совместимость 7-22
              IBM PS/2 80286 опция увеличенной памяти 7-22
         Интерфейс прикладной программы EMS 7-23
              Конфликт прерываний 7-24
              Языки высокого уровня 7-24
              Обработка условий ошибок 7-24
         Написание программ, использующих расширенную память 7-28
              Общие руководящие указания по программированию 7-28
              Применение расширенной  памяти  в  нерезидентных
              программах 7-30
              Обнаружение наличия менеджера расширенной памяти 7-30
              Метод открытого обработчика 7-30
              Проверка версии спецификации расширенной памяти,
              поддерживаемой менеджером расширенной памяти 7-31
              Определение доступного объема расширенной памяти 7-32
              Размещение расширенной памяти 7-32
              Адресация расширенной памяти 7-33
              Управление логическими адресами 7-34
              Управление физическими адресами 7-35
              Чтение и запись расширенной памяти 7-36
              Два способа задания физических страниц 7-37
              Разделение расширенной памяти между программами 7-38
              Выполнение кода в расширенной памяти 7-40
              Освобождение расширенной памяти 7-41
         Системное программное обеспечение 7-42
              Сравнение нерезидентных и резидентных программ 7-42
              Обнаружение наличия менеджера расширенной памяти 7-43
              Управление контекстом 7-43
              Переключение задач 7-45
              Неразрушаемая память 7-46
              Управление доступом 7-46
         Заключение 7-46
         Библиография 7-47
         Программы интерфейса низкого уровня и пример приложения 7-49
              О примере приложения 7-50
              Несколько соображений по кодированию 7-51


                 Глава 8. ПРОГРАММИРОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА 8-1

         Основы асинхронной последовательной связи 8-1
              Контроль по четности и обнаружение ошибок 8-4
              Связь с использованием стандарта RS-232C 8-4
              Управление потоком с помощью XON/XOFF 8-5
         Последовательный порт с точки зрения программиста 8-6
              Управляемый прерываниями последовательный ввод/вывод 8-8
              Прерывания последовательного адаптера 8-9
              Программирование контроллера 8259A 8-12
         Использование  средств  MS-DOS для программирования
         последовательного порта 8-13
              Драйвер, TSR или автономная программа 8-13
              Использование BIOS для последовательной связи 8-14
              Установка коммуникационных параметров с использованием
              BIOS 8-14
              Получение адреса последовательного порта 8-17
              Настройка на управляемый прерываниями последовательный
              ввод/вывод 8-17
              Обработка прерываний последовательного порта 8-19
              Очереди обработчика прерываний 8-21
              Уборка перед закрытием магазина 8-21
         Пример программы 8-22
         Заключение 8-32


                        Глава 9. ПРОГРАММИРОВАНИЕ EGA И VGA       9-1

         Мониторы и возможности EGA 9-2
              Усовершенствованный графический дисплей 9-2
              Монохромные графические режимы 9-3
              Соображения по установке и проверка наличия 9-5
              Организация памяти 9-11
              Регистры-защелки 9-12
         Прямая запись на экран 9-14
         Много точек 9-17
              Использование регистра установки/сброса 9-19
              Использование режимов записи EGA 9-20
         Чтение битовых матриц 9-24
         Цветовые палитры EGA 9-25
         Регистр циклического сдвига данных 9-28
         Режим отображения 256 цветов VGA 9-32
         Заключение 9-31

             Глава 10. ПРОГРАММИРОВАНИЕ РАСШИРЕНИЯ ЧИСЛОВОЙ ОБРАБОТКИ
                                    ФИРМЫ INTEL                        10-1

         NPX с точки зрения программиста 10-2
              Регистры данных в NPX 10-2
              Представление в NPX вещественных чисел с плавающей
              точкой 10-3
              Другие форматы данных, используемые в NPX 10-5
              Короткий вещественный и длинный вещественный
              форматы данных 10-5
              Целое слово, короткий целый и длинный целый
              форматы данных 10-6
              Форматы упакованного двоично-десятичного кода (BCD) 10-6
              Коротко о типах данных 10-7
              Набор команд NPX 10-9
              Префикс FWAIT 10-9
              Способы адресации NPX 10-12
              Команды FINIT и FFREE 10-13
              Управление NPX 10-14
              Слово состояния NPX 10-15
              Обработка особых ситуаций в NPX 10-18
         Использование средств MS-DOS с NPX 10-19
              Использование MASM и NPX 10-19
              NPX переключатели MASM - /r и /s 10-20
              Типы данных NPX в MASM 10-20
              Отладка регистров NPX 10-22
              Форматы кодировки команд 10-23
         Примеры программирования NPX с помощью MASM 10-23
              Команды FWAIT и FINIT 10-23
              Программа DUMP87 10-23
              Использование программы DUMP87 10-32
              Использование NPX для преобразований
              двоичного кода в десятичный  10-34
              Операции с целым 10-34
              Операции с плавающей запятой 10-35
              Вычисления в 2-ной системе 10-35
              Вычисления в 10-ричной системе 10-36
              Функция масштабирования десятичного в вещественное 10-36
              Функция масштабирования вещественного в десятичное 10-37
         Заключение 10-44


                             ЧАСТЬ III. ВОССТAHОВЛЕНИЕ             11-1


                 Глава 11. СТРУКТУРА ДИСКА И ВОССТAHОВЛЕНИЕ ФАЙЛОВ  11-1


         Основные принципы восстановления файлов 11-3
              Структура 40-трековых, односторонних, 5,25-дюймовых
              гибких дисков 11-3
              Структура 40-трековых, двухсторонних, 5,25-дюймовых
              гибких дисков 11-5
              Структура 80-трековых, двухсторонних, 5,25-дюймовых
              гибких дисков 11-5
              Сектор начальной загрузки 11-9
              Таблицы разделения жесткого диска 11-31
              Сектора каталога 11-33
              Имя файла, тип файла и состояние файла 11-33
              Атрибут 11-35
              Начальный кластер 11-36
              Размер файла 11-37
              Элементы каталога "." и ".." 11-37
              Cектора таблицы размещения файла (FAT) 11-38
              Декодирование элементов таблицы FAT 11-42
              Обработка 12-битовых входов таблицы FAT 11-46
              Обработка 16-битовых входов таблицы FAT 11-48
              Преобразование кластеров в логические сектора 11-48
         Обзор процедур восстановления 11-49
         Восстановление разрушенных файлов при помощи утилит
         CHKDSK и RECOVER 11-50
         Восстановление стертых файлов 11-51
              Основные принципы 11-51
              Восстановление стертых файлов аппаратным способом 11-54
              Использование программы контроля RESCUE 11-55
              Использование утилит Нортона 11-68
              Использование "Ultra-утилит" 11-69
         Заключение 11-70

                    Глава 12. ВОССТАНОВЛЕНИЕ ДАННЫХ, ПОТЕРЯННЫХ
                                     В ПАМЯТИ                      12-1

         Восстановление  после сбоев, произошедших во время обра-
         ботки текста или редактирования текста 12-1
         Восстановление программ на языке Бейсик из памяти 12-5
         Заключение 12-7



                              ЧАСТЬ IV. СОВМЕСТИМОСТЬ              13-1


                        Глава 13. РАЗЛИЧИЯ В ВЕPCИЯХ MS-DOS        13-1

         Общие рекомендации по совместимости 13-2
              Некоторые соображения относительно языков
              высокого уровня 13-6
         Прерывания MS-DOS 13-7
         Вызов функций 13-8
              Выполнение вызова функций стандартным образом 13-9
              Выполнение вызова функций в режиме совместимости 13-9
              Еще один способ (только для версий операционной  системы
              MS-DOS, начиная с 2.00 и выше) 13-10
              Функции, выполняемые в разных версиях операционной
              системы MS-DOS 13-10
              Группа завершения программы 13-21
              Группа стандартного ввода-вывода с символьных
              устройств (01h - 0Ch) 13-22
              Группа стандартного управления файлами
              (0Dh - 24h, 27h - 29h) 13-22
              Стандартные  функции,  не связанные с устройствами
              (25h,26h, 2Ah - 2Eh) 13-22
              Группа расширенных (общих) функций
              (2Fh - 38h, 4Ch - 4Fh,54h - 57h, 59h - 5Fh, 62h) 13-23
              Группа функций работы с каталогом (39h - 3Bh, 47h) 13-24
              Группа управления памятью/процессом (48h - 4Bh) 13-24
         Коды ошибок 13-24
              Коды критических и тяжелых ошибок (полученных при
              прерывании "Int 24h") 13-24
              Коды возврата ошибок обращения к функциям (только версий
              2.0 и выше операционной системы MS-DOS) 13-25
              Расширенная информация по ошибкам обращения к функциям
              (для версий 3.0 и выше операционной системы MS-DOS) 13-30
              Код ошибки 13-30
              Класс ошибки 13-31
              Предлагаемое действие 13-31
              Местоположение 13-32
         Форматы дисков 13-33
         Управление файлами 13-35
              Использование блоков управления файлами (FCB) 13-36
              Описатели файлов операционной системы MS-DOS 13-37
         Операционная система MS-DOS, персональный компьютер фирмы
         "ИБМ" IBM PC и персональный компьютер IBM PS/2 13-38
              Сходства 13-39
              Различия 13-40
         Совместимость с другими операционными  системами 13-41
              Операционная система CP/M-80 13-42
              Операционные системы СР/M-86 и "Concurrent CP/M-86" 13-44
              Операционные системы "Concurrent PC-DOS" и
              "Concurrent DOS-286" 13-44
              Операционные системы XENIX и UNIX 13-45
              Операционная система OS/2 13-45
         Заключение 13-46


                                ЧАСТЬ V. ПРИЛОЖЕНИЯ                17-1

                        Приложение А.   СРЕДСТВА РАЗРАБОТКИ        17-1

         Использование командных файлов для автоматизации процесса
         трансляции с языка Ассемблера П-1
         Использование командных файлов для макроассемблера  MASM
         версий с 1.00 по 5 П-1
         Использование  командных файлов для макроассемблера MASM
         версий 5 и выше П-3
         Использование средства MAKE фирмы "Майкрософт" П-7
         Использование шаблонов  для  создания программ с
         расширением ".COM" и ".EXE" П-9
         Использование библиотечных стандартных программ П-26


                   Приложение Б. НЕ ОПИСАННЫЕ В ДОКУМЕНТАЦИИ ПО
                 ОПЕРАЦИОННОЙ СИСТЕМЕ MS-DOS ПРЕРЫВАНИЯ И ФУНКЦИИ  П-43

         Не описанные в  документации прерывания операционной
         системы MS-DOS П-43
              Прерывание 28h(40): прерывание по безопасности DOS
              Прерывание 29h(41): Вывод на устройство консоли
              Прерывания с 2Ah(42) по 2Dh(45) : внутренние стандартные
               программы операционной системы MS-DOS
              Прерывание 2Eh(46). "Черный ход" для командного
               процессора
              Прерывания с 30h(48) по FFh(255)
         Не описанные в документации вызовы функций прерывания 21h(33)
              Функции  18h(24), 1Dh(29), 1Eh(30), 20h(32h): формальные
               функции, обеспечивающие совместимость с операционной
               системой CP/M
              Функция 1Fh(31): найти информацию о блоке на диске для
               текущего диска
              Функция 32h(50) : найти информацию о блоке на диске
               для указанного диска
              Функция  34h (52): получить флаг занятости операционной
               системы MS-DOS
              Функция 37h(55): получить/установить символ переключения
              Функция 50h(80): установить сегмент PSP
              Функция 51h(81): Считать сегмент PSP
              Функция 52h(82): Считать адрес "списка списков"
               операционной системы MS-DOS
              Функция 53h(83): Преобразовать блок параметров BIOS (BPB)
               в блок на диске
              Функция 55h(85): Создать блок PSP
              Функция 58h(88): Получить/установить стратегию
               распределения памяти
              Функция 60h(96): Разложить строку пути доступа на строку
               с полностью уточненным путем доступа
              Функция 63h(99): Получить таблицы начального байта

                            Приложение В.   ЛИТЕРАТУРА             П-52

         Книги П-52
         Статьи П-53

                     Приложение Г.  СПРАВОЧНИК ПО КОДАМ ASCII
                              И ПРЕОБРАЗОВАНИЯ ЧИСЕЛ               П-54

         Описания непечатаемых  символов  ASCII П-57
         Преобразование шестнадцатиричного кода в десятичный П-59
         Преобразование десятичного кода в шестнадцатиричный П-60

                Приложение Д. СТРУКТУРЫ ОПЕРАЦИОННОЙ СИСТЕМЫ MS-DOS П-64

         Структура каталога П-64
         Блок параметров базовой системы ввода-вывода BIOS (BPB) П-64
         Формат сегмента префикса программы (PSP) П-66
         Заранее определенные описатели  файла П-66
         Коды возврата П-66
         Коды ошибок операционной системы MS-DOS версий с 2.00 по 4.0
         Коды ошибок операционной системы MS-DOS версий с 3.0 по 4.0
         Коды ошибок операционной системы MS-DOS версий с 3.1 по 4.0
         Коды ошибок операционной системы MS-DOS версий с 3.3 по 4.0 П-67
         Прерывания операционной системы MS-DOS П-69
              Прерывание 20h - Завершить программу
              Прерывание 21h - Запрос на вызов функции
              Прерывание "int 23h"
                AH = 00h - Завершить программу
                AH = 01h - Ввести символ с консоли с эхом
                AH = 02h - Ввести символ на консоль
                AH = 03h - Ввести символ со вспомогательного порта
                AH = 04h - Вывести символ на вспомогательный порт
                AH = 05h - Вывести символ на печатающее устройство
                AH = 06h - Назначить консольный ввод/вывод
                AH = 07h - Назначить консольный ввод символа без эха
                AH = 08h - Назначить консольный ввод символа без эха
                AH = 09h - Вывести строку на консоль
                AH = 0Ah - Ввести буферизованную строку с консоли с эхом
                AH = 0Bh - Проверить состояние стандартного устройства
                AH = 0Ch - Очистить буфер клавиатуры и вызвать функцию
                           работы с клавиатурой
                AH = 0Dh - Сброс/переустановка диска
                AH = 0Eh - Выбрать диск
                AH = 10h - Закрыть файл с помощью блока FCB
                AH = 11h - Поиск первой записи в FCB
                AH = 12h - Поиск следующей записи в FCB
                AH = 13h - Удалить через FCB файл
                AH = 14h - Последовательно считать  FCB
                AH = 15h - Последовательная запись  FCB
                AH = 16h - Создать файл через FCB
                AH = 17h - Переименовать файл через FCB
                AH = 19h - Получить текущий диск
                AH = 1Ah - Установить адрес передачи на  диск
                AH = 1Bh - Получить информацию таблицы распределения
                AH = 1Ch - Получить информацию таблицы распределения
                           для указанного устройства
                AH = 21h - Произвольное считывание файла
                AH = 22h - Произвольная запись в файл
                AH = 23h - Получить размер файла
                AH = 24h - Установить поле относительной записи
                AH = 25h - Установить вектор прерывания
                AH = 26h - Создать новый сегмент префикса программы
                AH = 27h - Считать произвольный блок файла
                AH = 28h - Запись в произвольный блок файла
                AH = 29h - Проанализировать имя файла FCB
                AH = 2Ah - Получить дату
                AH = 2Bh - Установить дату
                AH = 2Ch - Получить время
                AH = 2Dh - Установить время
                AH = 2Eh - Установить/сбросить переключатель проверки
                AH = 2Fh - Получить адрес передачи диска (DTA)
                AH = 30h - Получить номер версии операционной системы
                AH = 31h - Завершить процесс и остаться резидентным
                AH = 33h - Получить/установить статус проверки
                           "Ctrl-Break"
                AH = 35h - Получить вектор прерывания
                AH = 36h - Получить свободное пространство памяти на
                           диске
                AH = 38h - Получить текущую информацию о стране
                AH = 38h - Установить информацию, относящуюся к стране
                AH = 39h - Создать подкаталог (MKDIR) стране
                AH = 3Ah - Удаление подкаталога (RMDIR)
                AH = 3Bh - Изменение текущего каталога (CHDIR)
                AH = 3Ch - Создать файл (СREATE)
                AH = 3Dh - Открыть файл
                AН = 3Eh - Закрыть описатель файла
                AH = 3Fh - Считать с файла или с устройства
                AH = 40h - Записать в файл или на устройство
                AH = 41h - Удалить файл из указанного каталога
                AH = 42h - Передвинуть указатель считывания/записи
                           файла (LSEEK)
                AH = 43h - Изменить режим файла (CHMOD)
                AH = 44h - Управление устройствами ввода/вывода
                           (IOCTL)
                AH = 45h - Дублировать описатель файла (DUP)
                AH = 46h - Вынужденное дублирование описателя файла
                           (FORCDUP)
                AH = 47h - Получить текущий каталог
                AH = 48h - Распределить память
                AH = 49h - Освободить распределенную память
                AH = 4Ah - Модифицировать распределенные блоки памяти
                           (SETBLOCK)
                AH = 4Bh - Загрузить или выполнить программу (EXЕС)
                AH = 4Ch - Завершить процесс (EXIT)
                AH = 4Dh - Получить код возврата подпроцесса (WAIT)
                AH = 4Eh - Найти первый совпавший файл (FINDFIRST)
                AH = 4Fh - Найти следующий совпадающий файл (FINDNEXT)
                AH = 54h - Получить установку верификации
                AH = 56h - Переименовать файл
                AX = 5700h - Получить дату и время файла
                AX = 5701h - Установить дату и время файла
                AH = 59h - Получить расширенную информацию об ошибке
                AH = 5Ah - Создать временный файл
                AH = 5Bh - Создать новый файл
                AH = 5Ch - Блокировать/Разблокировать доступ к файлу
                AX = 5E00h Сеть: Получить имя ЭВМ
                AX = 5E02h Сеть: Установить строку установки принтера
                AX = 5E03h Сеть: Получить строку установки принтера
                AX = 5F02h Сеть: Получить элемент списка перена-
                           значения
                AX = 5F03h Сеть: Переназначить устройство
                AH = 62h Получить адрес сегмента  префикса программы
                AH = 65h Получить расширенную информацию, относящуюся
                         к стране [3.3] [4]
                AH = 66h Получить/установить глобальную кодовую
                         страницу
                AH = 67h Установить счетчик описателя
                AH = 68h Передать файл
                AH  = 69h Расширенная функция открыть/создать
              Прерывание 22h - Завершить адрес
              Прерывание 23h - Адрес выхода по Ctrl-Break
              Прерывание 24h - Адрес обработчика критических ошибок
              Прерывание 25h - Считывание абсолютное =< 32-Мбайтный
               диск) и 26h (Запись абсолютная =< 32-Мбайтный диск)
              Прерывание 25h - Считывание абсолютное > 32-Мбайтный
               диск) и 26h (Запись абсолютная > 32-Мбайтный диск)
              Прерывание 27h - Завершиться и остаться резидентным
              Прерывание 2Fh - Вызовы функции мультиплексного
                               прерывания
              Прерывание 67h - Менеджер расширенной памяти (EMS)
              Коды ошибок/состояния LIM EMS 3.Х, 4.0 MS-DOS и
              AQA EEMS3.Х П-90 - П-98


             Глава 1. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ 1: ИНСТРУМЕНТАЛЬНЫЕ
                      СРЕДСТВА СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

             Потребность в короткой записи операторов
             Введение в МАКРОСЫ
             Условное  ассемблирование
             Условное ассемблирование и МАКРОСЫ
             Структурные операторы управления в языке Ассемблер
             Макросы данных
             Макросы генерации программного кода
             Применение директивы STRUC

            Когда программисты-фанатики  собираются  в своем кругу для об-
         суждения тайн структурного программирования, разговор обычно кон-
         центрируется   на   небольшом   наборе   конструкций  языка  типа
         IF-THEN-ELSE. Приверженец языков Паскаль или Си будет читать лек-
         цию о преимуществах языков высокого уровня по сравнению с языками
         ассемблерного типа.  Вероятно,  будут приведены горячие аргументы
         по поводу использования оператора GOTO.  Несмотря на все предыду-
         щие обсуждения,  ясно,  что сказано далеко не все. В действитель-
         ности,  подобное  обсуждение  фокусируется  только на структурном
         программировании.  Как Вы скоро увидите, структурное программиро-
         вание возможно на любом языке. Модные структуры управления языков
         высокого уровня поддерживают даже некоторые ассемблеры.  Одним из
         таких  ассемблеров является  Макро Ассемблер фирмы Майкрософт для
         операционной системы MS-DOS, широко известный под названием MASM.

                     Потребность в короткой записи операторов
            Прежде чем представить структуры управления высокого  уровня в
         языке  Ассемблера мы рассмотрим некоторые преимущества языков вы-
         сокого уровня.  Так или иначе все заканчивается на уровне ассемб-
         лера. Что же выигрывается от использования языка высокого уровня?
            Возможность выражать программистскую идею в форме, легко пони-
         маемой программистом или специалистом.  Предположим,  что каждому
         оператору ассемблера в большей или меньшей  степени соответствует
         одна машинная команда.С другой стороны, один оператор языка высо-
         кого уровня может расширяться до десятков или даже сотен машинных
         инструкций.  (Тот,  кто сомневается в расширении до сотен,  может
         проверить это,  проанализировав работу вычислительной подпрограм-
         мы на языке Фортран, имеющей множество аргументов).
            На Рис.1-1 показан фрагмент одной и той же программы, реализо-
         ванный на Фортране и языке Ассемблера 8086.  Этот фрагмент вычис-
         ляет сумму 1...  NUM для данного NUM. Нет сомнений, что программа
         на  ассемблере  прежде  всего  может  быть оптимизирована с целью

                 Фортран                      Aссемблер

                 sum = 0                      mov  sum,0
                 DO 100 I = 1, NUM            mov  ax,1
             100 SUM =SUM +I          loop1:  cmp  ax,num
                                              jg   loop1_end
                                              add  sum,ax
                                              inc  ax
                                              jmp  loop1
                                      loop1_end:
             Рис. 1-1. Сравнение Фортрана с Ассемблером

                                      - 1-2 -
         уменьшения или количества выполняемых команд,  или времени выпол-
         нения программы. Но вне зависимости от того, как к этому подойти,
         легче написать программу на Фортране,  чем на Ассемблере. Для на-
         писания программы на Ассемблере должно быть принято гораздо боль-
         ше решений.  В силу значительной сложности работы по программиро-
         ванию на ассемблере ошибки кодирования  более  вероятны.  Я  могу
         быть уверен,  что программа на Фортране будет выполняться превос-
         ходно,  но я не могу сказать этого о программе на ассемблере. По-
         чему возникают такие сомнения? Потому что каждая строка программы
         на Фортране представляет собой законченную мысль,  в то время как
         ассемблерная  программа  требует для реализации той же мысли мно-
         жества строк.
            Короче говоря,  использование конструкций высокого уровня при-
         водит к облегчению процесса программирования и  повышению  надеж-
         ности  программ.  Такие конструкции делают программирование менее
         сложным,  что позволяет программисту сконцентрироваться на логике
         программы. Естественно, что программисты хотели бы быть уверены в
         правильности результатов своей работы. Инструментальные средства,
         поддерживающие такую уверенность, приводятся в нижеследующих раз-
         делах.

                                Введение в МАКРОСЫ

            Таким образом, программирование на языке Ассемблера может быть
         значительно облегчено, если  иметь возможность создавать "стеног-
         рамму" часто используемых операторов.  MASM обеспечивает эту воз-
         можность через средства макро. Макро представляют собой "суперко-
         манды",  которые  разгружают  MASM  от  части  лишней   и   часто
         повторяющейся работы по обработке ассемблерной программы. При по-
         мощи макросов программисты определяют блоки ассемблерных операто-
         ров,  а  затем,  используя  конкретные ссылки,  указывают MASM на
         включение соответствующих блоков в ассемблерную программу. В этой
         главе  мы  рассмотрим некоторые из таких макросов и понемногу ра-
         зовьем Ваши способности по написанию собственных инструментариев.
         Все  это  позволит Вам соединить скорость выполнения ассемблерной
         программы с мощностью языка высокого уровня.
            Для создания  и использования макро необходимо выполнить 2 ша-
            га:
                             Шаг 1. Определение макро

                    ;; Определить    "Требуемую функцию" типа @DosCall
                    @DosCall        MACRO
                                    int 21h   ;для выполнения функции обра-
                                    ENDM      ;титься к MS-DOS
                            Шаг 2. Использование макро

                                    @DosCall       <--- вызов макро

            В листинге появится следующее:

                                   @DosCall        <--- вызов макро
                    1              int 21h    ;для выполнения функции обра-
                                              ;титься к MS-DOS

            При ассемблировании программы оператор DosCall  заменяется  на
         оператор  int  21h,  включая комментарий.  Файл листинга содержит
         строку DosCall как ссылку,  однако объектный файл содержит только

                                      - 1-3 -
         код для инструкции int 21h. Такая операция известна под названием
         "подстановка макро" или " расширение макро".
            Заметьте, что  в  предыдущем  примере ассемблер вставил в файл
         листинга символ, обозначающий код расширенного макро. В MASM вер-
         сии 4 и выше "1" помещается в строки, принадлежащие первому уров-
         ню макрорасширения,  "2" используется для второго уровня и т.д. В
         MASM  версии 3 и предыдущих версий все строки макрорасширения вне
         зависимости от уровня помечаются символом плюс (+).
            При обработке  ассемблером ссылка на макро заменяется на прог-
         раммный код, который это макро представляет. Макро не вырабатыва-
         ет команду СALL (вызвать),  обращенную к коду макро,  хотя ссылки
         на макро порой и используют такой путь.
            Подобно другим  конструкциям в программировании макросы должны
         следовать строгим правилам. Форма описания макроса следующая:

            mname      MACRO     argument_list
                       .
                       .      <--- тело макрокода
                       .
                       ENDM

            Имя макро определяется как mname, а argument_list представляет
         собой список аргументов,  разделенных запятыми. Если макро не со-
         держит аргументов (как в нашем примере с @DosCall),  список аргу-
         ментов может быть пуст.
            Выше был приведен простейший пример. Если это было бы все, что
         умеет делать макро, то тогда оно было бы довольно примитивным об-
         разованием. К счастью, макросы можно подгонять к конкретным усло-
         виям применения, используя секцию аргументов. Следующее макро яв-
         ляет собой пример подобной настройки.

            ;; Определить "Печать символа" как PrintChr
            @PrintChr  MACRO    char
                       mov  ah,05
                       mov  dl,&char
                       @DosCall
                       ENDM
         И теперь при использовании макро мы пишем:

            @PrintChr 'A'      <--- вызов макро,

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

            @PrintChr 'A'     <--- вызов макро
            1   mov  ah,05
            1   mov  dl,'A'
            2   int 21h   ;для выполнения функции обратиться к MS-DOS

            Конструкция "&char" в макроописании была заменена после вызова
         макро на "A". (Да, мы ссылаемся на макро, как если бы стоял вызов
         call. Это удобно, особенно если вспомнить, что команда CALL в яв-
         ном виде не используется.) Цифра,  появляющаяся в начале  строки,
         представляет  собой  способ,  при  помощи  которого MASM сообщает
         программисту,  что текущий код является результатом макрорасшире-
         ния.  Также заметим, что макро @PrintChr содержит ссылку на ранее
         определенное  макро  @DosCall,  которое  расширяется  в  оператор
         int 21h,  его  представляющий. MASM продолжает "раскручивать" вы-

                                      - 1-4 -
         зовы макро до такого уровня, до которого они вложены, пока не пе-
         реполнится область памяти таблицы  символов.  Вложенность  являет
         собой другой способ сообщения, что макро может вызвать макро, ко-
         торое в свою очередь может вызвать следующее макро и т.д.
            Имя char в макро @PrintChr  называется  формальным  аргументом.
         Всякий раз, когда формальный аргумент char появляется в макро, он
         заменяется на значение, использованное при вызове макро. В приме-
         ре  с  @PrintChr  замена char означает,  что все появления сhar в
         макро заменяются на "A".
            Заметим, что любое имя,  выбранное  для  формального  аргумен-
         та,используется исключительно для этого аргумента. Таким образом,
         если Вы для формального аргумента выбрали имя AX, Вы не можете  в
         данном макро ссылаться на регистр AX!
            Аналогичное предупреждение  действует  и  для именования собс-
         твенно макро. Как только для описания макро Вы выбрали имя add,Вы
         найдете,  что все ссылки на код операции ADD будут вырабатывать в
         данной программе расширение макро add.  При желании,  таким обра-
         зом,  можно изменять директивы MASM. Однако очень важно не созда-
         вать для имен конфликтные ситуации.
            Символ "&" перед char в макро @PrintChr используется  для  до-
         бавления в строку mov dl,  значения char. Символ "&" не нужен для
         раскрутки формального аргумента,  что происходит и так,  а  нужен
         для сообщения MASM, что char является формальным аргументом, а не
         частью более длинной строки "mov dl,char". Как показано в следую-
         щем примере,  оператор "&" особенно важен,  если формальные аргу-
         менты содержатся в длинных строках.

            Макроописание             Макрорасширение
            @Example MACRO  arg       @Example Y
               mov   dl,arg           1  mov  dl,y    <-правильно
               mov   dl,&arg          1  mov  dl,y    <-правильно
               mov   dl,argZ          1  mov  dl,argZ
               mov   dl,&argZ         1  mov  dl,argZ
               mov   dl,arg&Z         1  mov  dl,YZ   <-правильно
               mov   dl,Xarg          1  mov  dl,Xarg
               mov   dl,X&arg         1  mov  dl,XY   <-правильно
               mov   dl,XargZ         1  mov  dl,XargZ
               mov   dl,X&argZ        1  mov  dl,XargZ
               mov   dl,Xarg&Z        1  mov  dl,XargZ
               mov   dl,X&arg&Z       1  mov  dl,XYZ   <-правильно
               ENDM

            Строго говоря, в макро @PrintChr символ "&" не требуется. MASM
         имеет возможность определить, что char - формальный аргумент, так
         как  после  запятой он присутствует в одиночестве.  Тем не менее,
         это хорошая привычка использовать символ "&",  даже когда  он  не
         требуется,  так  как  он  выделяет формальный аргумент при чтении
         макро и проясняет для MASM, что имелось в виду.


                                 Метки типа LOCAL

            До сих пор макросы, использованные нами, предназначались  лишь
         для  генерации  простых ассемблерных команд.  Теперь предположим,
         что мы хотим создать макро,  которое выбирает меньшее из двух чи-
         сел  и  помещает  результат в какую-то ячейку.  Такое макро может
         выглядеть, например, так:

                                      - 1-5 -
            min  MACRO    result,first,second
                 mov      &result,&first
                 cmp      &first,&second
                 jl       order_ok
                 mov      &result,&second
            order_ok:
                 ENDM

            Когда мы вызываем макро min,  оно вырабатывает правильный код,
         однако имеется одна проблема: хотя макро вычисляется превосходно,
         оно может быть использовано лишь единожды. Так как метка order_ok
         может быть определена в программе только один раз,  при использо-
         вании данного макро в двух местах программы MASM распознает  мно-
         жественное определение символа.
            Чтобы в добавление к другим параметрам разрешить указание  па-
         раметра метки, мы можем выполнить небольшое изменение макро:

            min   MACRO     result,first,second,order-ok
                  mov       &result,&first
                  cmp       &first,&second
                  jl        &order_ok
                  mov       &result,&second
            оrder_ok&:
                  ENDM
            При вызове нового макро min,  показанного в следующем  номере,
         мы можем указать имя,  которое будет использоваться для метки пе-
         рехода. Теперь макро min может быть использовано всякий раз, ког-
         да это необходимо,  так как каждый раз метке перехода будет прис-
         ваиваться новое имя.  Однако действительное имя не имеет для  нас
         никакого значения, ибо метка является собственностью функции min.

                  min       ax,bx,cx,jmp1        <- вызов макро
            1     mov       ax,bx
            1     cmp       bx,cx
            1     jl        jmp1
            1     mov       ax,cx
            1  jmp1:

            Такой способ создания нового имени при каждом вызове макро min
         является лучшим.  Именно для этой цели MASM имеет директиву LOCAL
         (локальный). Когда MASM встречает LOCAL, для стоящего рядом имени
         создается уникальная метка. Другой способ заключается в помещении
         параметра LOCAL в список параметров MACRO,  но при этом MASM про-
         изводит  присваивание действительного аргумента.  Предупреждение:
         операторы LOCAL всегда должны помещаться сразу  же  после  строки
         именования MACRO! После включения директивы LOCAL новое макро min
         выглядит так:
            min    MACRO     result,first,second
                   LOCAL     order_ok
                   mov       &result,&first
                   cmp       &first,&second
                   jl        order_ok
                   mov       &result,&second
            order_ok:
                   ENDM

                                      - 1-6 -

            Теперь, когда мы снова вызовем макро min, образуется листинго-
         вый файл, как показано в следующем примере. Значение order_ok бу-
         дет заменено на ??0000.  Каждый раз при вызове макро order_ok за-
         меняется на новое значение, вырабатываемое MASM.

                    min       ax,bx,cx   ;первый вызов
            1       mov       ax,bx
            1       cmp       bx,cx
            1       jl        ??0000
            1       mov       ax,cx
            1  ??0000:
                    min       ax,bx,cx   ;второй вызов
            1       mov       ax,bx
            1       cmp       bx,cx
            1       jl        ??0001
            1       mov       ax,cx
            1  ??0001:

            Конечно, остается  вероятность  возникновения конфликта меток,
         если Вы решите использовать метки, начинающиеся с ??. Если Вы ус-
         траните использование меток, начинающихся с ??, то Вы сможете вы-
         зывать макро min столько раз, сколько захотите.
            Использование меток  LOCAL не ограничивается только переходами
         по адресам. Метки LOCAL могут также использоваться с данными, как
         показано  в следующем макросе.  В этом случае макрос используется
         для вставки текстовых строк в сегмент данных и последующего  соз-
         дания ссылки на них в сегменте кода.
            Сравнивая исходный текст с макрорасширением  в  Листинге  1-1,
         можно увидеть, насколько удобно использовать макрос. Листинг  1-1
         также содержит некоторые полезные  макросы,  облегчающие  решение
         задачи по написанию программ .ЕХЕ. Однажды определив эти макросы,
         Вы можете не беспокоиться  за  правильность  синтаксиса  программ
         .ЕХЕ!

                        Листинг 1-1. Программа Hello World
             -------------------------------------------------------------
            ;********************************************************
            ;        С Е К Ц И Я   М А К Р О О П И С А Н И Я
            ;********************************************************
            ;
            @DosCall  MACRO
                      int      21h  ;вызвать функцию MS-DOS
                      ENDM
            ;
            @InitStk  MACRO         ;определить размер стека
            stk_seg   SEGMENT  stack
                      DB       32 dup ('stack   ')
            stk_seg   ENDS
                      ENDM
            ;
            @InitPrg  MACRO    segment  ; инициализировать сегмент
                      ASSUME ds:segment ; данных
            start:                      ; основная точка входа
                      mov      ax,segment

                                      - 1-7 -
                      mov      ds,ax    ;установить сегмент данных
                      mov      es,ax    ;установить внешний сегмент
                      ENDM
            ;
            @Finis    MACRO
                      mov      ax,4C00h ;завершить процесс
                      @DosCall
                      ENDM
            ;
            @DisStr   MACRO    string   ;отобразить строку памяти
                      mov      dx,offset string
                      mov      ah,09h
                      @DosCall
                      ENDM
            ;
            @TypeStr  MACRO     string  ;определить и отобразить строку
                      LOCAL     saddr   ;установить локальную метку
            cod_seg   ENDS              ;завершить сегмент кода
            dat_seg   SEGMENT           ;перейти к сегменту данных
            saddr     DB    string,'$'  ;определить строку в сегм.данных
            dat_seg   ENDS              ;завершить сегмент данных
            cod_seg   SEGMENT           ;вернуться к сегменту кода
                      @DisStr   saddr   ;отобразить строку
                      ENDM
            ;
            ;
            ;********************************************************
            ;         П Р О Г Р А М М Н А Я    С Е К Ц И Я
            ;********************************************************
            ;
                      @IniStk                ;установить стек
            cod_seg SEGMENT                  ;определить сегмент кода
            main    PROC        FAR          ;главная (одна) процедура
                    ASSUME      cs:cod_seg   ;назначить сегм.кода рег.CS
                    @InitPrg dat_seg         ;инициализ-ать сегмент кода
                    @TypeStr 'Hello world!'  ;выдать приветствие
                    @Finis                   ;закончить программу
            main    ENDP                     ;завершить процедуру
            cod_seg ENDS                     ;завершить сегмент кода
                    END         start        ;завершить программу и ...
                                             ;определить адрес пуска
            -------------------------------------------------------

            Программу можно вводить точно так,  как она приведена, а затем
         ее ассемблировать и  выполнять.  Слова  "Hello  world!"  подлежат
         отображению на экране.Сам по себе результат не очень выразителен,
         однако,  если используемый макрос сохранен в файле include (вклю-
         чить),  написание .EXE-программ значительно облегчается.  Давайте
         посмотрим на распечатку расширения программы, приведенного в Лис-
         тинге 1-2.


                                      - 1-8 -
                Листинг 1-2. Макрорасширение программы Hello World
             -------------------------------------------------------------
            ;********************************************************
            ;           П Р О Г Р А М М Н А Я   С Е К Ц И Я
            ;********************************************************
            ;
            ;       @InitStk                 ;установить стек
            1       stk_seg   SEGMENT stack
            1                 DB      32 dup ('stack   ')
            1       stk_seg   ENDS
             cod_seg          SEGMENT        ;определить сегмент  кода
             main             PROC   FAR     ;главная (одна) процедура
                    ASSUME    cs:cod_seg     ;назначить сегм.кода рег.CS
                    @InitPrg  dat_seg        ;инициализ-ать сегмент данных
            1       start:                   ;главная точка входа
            1                 mov  ax,dat_seg
            1                 mov  ds,ax     ;установить сегмент данных
            1                 mov  es,ax     ;установить внешний сегмент
                    @TypeStr 'Hello World!'  ;выдать приветствие
            1       cod_seg   ENDS           ;приостановить сегмент кода
            1       dat_seg   SEGMENT        ;перейти к сегменту данных
            1       ??0000    DB   'Hello world!,'$' ;определить строку
            1       dat_seg   ENDS        ;приостановить сегмент данных
            1       cod_seg   SEGMENT     ;вернуться к сегменту кода
            2                 mov   dx,offset ??0000
            2                 mov   ah,09h
            3                 int   21h      ;вызвать функцию MS-DOS
                    @Finis                   ;завершить программу
            1                 mov   ax,4C00h ;завершить процесс
            2                 int  21h       ;вызвать функцию MS-DOS
                    main      ENDP           ;закончить процедуру
                    cod_seg   ENDS           ;закончить сегмент кода
                    END       start          ;закончить программу ...


            Прежде всего необходимо заметить,  что используемый  локальный
         адрес  (saddr)  в  @TypeStr  отлично работает как метка оператора
         данных.  При связывании меток с данными не используйте  двоеточие
         (:). Далее посмотрим, как макрорасширение использует зарезервиро-
         ванное слово SEGMENT (сегмент) в  макро  @InitPrg.  Нет  проблем!
         Вспомните,  что  имена  формальных аргументов в списке аргументов
         перекрывают все другие описания MASM.
            Обратите внимание,  что некоторые строки не включены в листин-
         говый файл.  Например,  оператор ASSUME ds:data_seg  из  @InitPrg
         опущен.  Оператор был отассемблирован,  но MASM подавил вывод его
         расширения.
            Все это произошло по причине специфики обработки  макросов. По
         умолчанию, исходные строки, не вырабатывающие исполнительного ко-
         да,  в листинге подавляются.  Оператор ASSUME является директивой
         MASM,  которая не вырабатывает собственного кода;  таким образом,
         он в листинге отсутствует. С другой стороны, директивы завершения
         сегмента ENDS приводятся в листинге,  хотя программный код не вы-
         рабатывают. Есть в MASM тайны, над которыми всем нам стоит пораз-
         мышлять.
            Представленную программу не следует рассматривать,  как эталон
         хорошего  программирования.  Хотя  идея  использования макросов в
         вводной и  заключительной  части  .EXE-программ  и  замечательна,
         включение  имен "важных" символов в сами макросы применяется ред-
         ко.  Если имя сегмента данных отличается от dat _seg, в программе
         может  возникнуть  нежелательная конфликтная ситуация.  Например,
         когда макро @TypeStr должно передать имя dat_seg в качестве аргу-

                                      - 1-9 -
         мента или макро @InitPrg полагает,  что сегмент данных называется
         dat_seg.

                             Директивы листинга макро

            Если вы хотите увидеть полный листинг макро,  поместите в файл
         с  ассемблерной  программой директиву MASM .LALL.  Затем получите
         файл .LST и сравните его с первоначальным листингом нашего приме-
         ра.  Теперь оператор ASSUME ds:data_seg будет в листинге. Для из-
         менения режима листинга на обратный используйте  директиву .XALL.
         Она  вернет MASM в режим,  устанавливаемый по умолчанию.  Если Вы
         хотите подавить все макрорасширения, используйте директиву .SALL.


                                  Макробиблиотеки

            Термин "макробиблиотека" не совсем верен.  В действительности,
         макробиблиотеки совсем не то,  что под  этим  могли  бы  понимать
         программные средства LINK /редактор/ и LIB /обработчик библиотек/
         фирмы Майкрософт.Макросы должны подключаться во время компиляции,
         так  как  они  представляют собой директивы для MASM и только для
         MASM.  Средства LINK и LIB не знают,  что делать с  ними.  Вместо
         этого макробиблиотеки являют собой файлы типа include (включить).
         Они могут определяться в отдельном  файле,  называемом  MYLIB.MAC
         или  STANDARD.  MLB  или как-нибудь еще  (Вы можете выбрать любое
         допустимое имя), и подключаться при  ассемблировании  посредством
         помещения в исходный текст программы директивы include. Например,

             INCLUDE  C:\ASМ\LIB\STANDARD.MLB

            Правила написания имени и указания накопителя те же, что и для
         всей  системы.  В  файле  листинга  строки,  полученные  из файла
         include, начинаются с буквы "C", равно как строки макрорасширения
         начинаются с плюса "+" (в версиях MASM ниже 4.0) или номера уров-
         ня расширения. Конечно, если у Вас большая библиотека и Вы не хо-
         тите загромождать файл .LST макроописаниями, при помощи директивы
         .XLIST "выключите" листинг перед include,  а затем "включите" его
         обратно (после include),  применяя директиву .LIST.
            Использование макробиблиотек  обосновывает  введение следующих
         макродиректив.  Хотя довольно редко сначала  определяют  макро  в
         программе,  а  затем отменяют такое определение (Вы бы его скорее
         просто уничтожили!),  для использования нескольких макроопределе-
         ний Вы вполне можете подключить макробиблиотеку.  Оставшиеся мак-
         роопределения занимают значительное пространство памяти в таблице
         символов MASM и в области памяти макро.  Вернуть эту память можно
         при помощи директивы PURGE  (очистить).  PURGE  позволяет  изъять
         описания  указанных макро.  Для изъятия макроописаний предыдущего
         примера следует выдать директиву:

            PURGE @DosCall,@InitStk,@InitPrg,@Finis,@DisStr,@TypeStr

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





                                      - 1-10 -
                         Макродиректива повторения - REPT

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

            file_head    MACRO       fnum
            file_hand_&fnum   dw     ?     ;заголовок файла
            file_nmax_&fnum   db     49    ;макс.длина имени файла
            file_nlen_&fnum   db     ?     ;действит.длина имени файла
            file_name_&fnum   db     50 dup (?) ;буфер имени файла
                           ENDM

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

            file_head       1      ;1-ый блок файла
            file_head       2      ;2-ой блок файла

            Вместо этого,  используя директиву REPT (повторить),  мы можем
         написать file_head так, чтобы он определял столько блоков, сколь-
         ко необходимо. Такой макрос приведен в Листинге 1-3.

                    Листинг 1-3. Описание блока доступа к файлу
            ------------------------------------------------------------

            fcnt           =            0  ;определить и иниц-ать символ
            file_head2     MACRO       fnum
            file_hand_&fnum     dw      ?      ;заголовок файла
            file_nmax_&fnum     db      49     ;макс.длина имени файла
            file_nlen_&fnum     db      ?      ;действ.длина имени файла
            file_name_&fnum     db      50 dup (?)  ;буфер имени файла
                           ENDM
            file_head      MACRO        fnum
                           REPT         fnum   ;повторить блок "fnum" раз
                           file_head2   %fcnt  ;создать блок  #"fcnt"
            fcnt           =    fcnt + 1
                           ENDM                ;закончить блок повторения
                           ENDM                ;закончить макро file_head
            -------------------------------------------------------------

            Как показано в Листинге 1-4, при вызове макро file_head оно, в
         свою очередь, дважды вызывает макро file_head2,  используя каждый
         раз новое значение fnum. Конечно, это макрорасширение со значени-
         ем статуса листинга,  установленного по умолчанию,  не показывает
         явно обращения к file_head2.  Однако результат работы REPT мы мо-
         жем  видеть по двум созданным блокам управления файлами. Заметим,

                                      - 1-11 -
         что директива REPT должна заканчиваться строкой ENDM, равно как и
         директива MACRO. Вcе блоки повторения должны  заканчиваться  ENDM
         (конец макро). Аналогично ENDM должно появляться в конце  каждого
         макроопределения.

           Листинг 1-4.  Макрорасширение блока описания доступа к файлу
         -----------------------------------------------------------------

                         file_head 2
            3       file_hand_0    dw   ?    ;заголовок файла
            3       file_nmax_0    db   49   ;макс. длина имени файла
            3       file_nlen_0    db   ?    ;действит.длина имени файла
            3       file_name_0    db   50 dup (?)  ;буфер имени файла
            3       file_hand_1    dw   ?    ;заголовок файла
            3       file_nmax_1    db   49   ;макс. длина имени файла
            3       file_nlen_1    db   ?    ;действит.длина имени файла
            3       file_name_1    db   50 dup (?)  ;буфер имени файла
         ------------------------------------------------------------------

            В добавление к директиве REPT мы также  использовали  счетчик.
         Счетчик - это переменная,  имеющая цифровое значение. Так как его
         значение может меняться, он должен быть определен при помощи опе-
         ратора присваивания (=). (В MASM для определения переменных, име-
         ющих статические значения,  используется  оператор  equ  (прирав-
         нять),  в  то время как для определения переменных,  чьи значения
         могут изменяться,  применяется знак равенства (=).) Счетчик, при-
         меняемый  в  макро  file_head,  называется  fcnt (счетчик файла).
         Счетчик fcnt увеличивается на 1 при каждом проходе  file_head. Но
         почему метки находятся в file_head2,  file_hand_0 и т.д.,  а не в
         file_hand_fcnt?  Каким образом имя fcnt заменяется на свое значе-
         ние?   Ответ   заключается   в  операторе  "%",стоящем  в  вызове
         file_head2 перед fcnt.  Знак процента предписывает замену символа
         на его значение. Так как мы использовали знак процента, нам необ-
         ходимы два макро.  Если бы мы попытались вычислить  и  подставить
         fcnt в одно маkро:

                            REPT      fnum   ;повторить блок "fnum" раз
            file_hand_&%fcnt       dw   ?         ;заголовок файла   ,

            возникла бы ошибка символа:


            file_hand_fcnt         dw   ?         ;заголовок файла

            Оператор процента (%) работает только в аргументах  макровызо-
         ва! Кроме того, значение символа должно быть абсолютной  (непере-
         мещаемой) константой.
            Другим важным  аспектом  наших макро является то,  что счетчик
         fcnt инициализируется вне макроблока. Это делается потому, что мы
         не хотим устанавливать  fcnt  в 0 всякий раз при вызове file_head
         (что может вызвать дублирование меток). Однако,  fcnt должен быть
         где-то инициализирован, или оператор:

            fcnt       =           fcnt +  1


                                      - 1-12 -
         вызовет появление сообщения об ошибке "Символ не определен".


             Более подробно о макродирективах повторения - IRP и IRPC

            Кроме директивы  REPT MASM поддерживает еще две директивы мак-
         роповторений.  Это - IRP (неограниченное повторение) и IRPC  (не-
         ограниченное повторение символа).  В действительности,  ничто не
         повторяется бесконечно. Вместо этого повторения происходят до тех
         пор, пока в списке аргументов есть хоть один аргумент. В Листинге
         1-5 приведен пример макроповторения,  названного test_vac и пред-
         назначенного для добавления элементов в сегмент данных.

             Листинг 1-5. Пример макро повторения IRP и его расширение
         ------------------------------------------------------------------

            test_mac    MACRO     args            ;определить "test_mac"
                        IRP       dummy,<&args>
                        db        dummy           ;добавить элемент
                        ENDM                      ;закончить "IRP"
                        ENDM                      ;закончить "test_mac"

                        test_mac   'one'                <-- 1-ый вызов
            2           db         'one'          ;добавить элемент
                        test_mac <'two','three','four'> <-- 2-ой вызов
            2           db        'two'           ;добавить элемент
            2           db        'three'         ;добавить элемент
            2           db        'four'          ;добавить элемент
         ------------------------------------------------------------------

            При каждом проходе блока повторения в качестве  значения dummy
         используется очередное значение списка аргументов.  Используя ди-
         рективу IRP,  мы можем для  выполнения  трех  действий  применить
         только один вызов макро.  При повторном вызове test_ mac блок IRP
         повторяется db раз для каждой из трех строк списка аргументов.
            Введем для  макросов  два специальных символа - угловые скобки
         (< и >).  Макро test_mac предполагает наличие только одного аргу-
         мента, а мы хотим переслать ему список аргументов. Угловые скобки
         выполняют эту функцию,  преобразуя текст,  заключенный в  них,  в
         одиночный литерал.  Таким образом, 'two','three','four' интерпре-
         тируется как один аргумент,  а не три. Однако сами угловые скобки
         принимающему  макро  не пересылаются.  Внутри test_mac args имеет
         значение 'two','three','four',  а не <'two','three','four'>.  Вот
         почему в директиве IRP были добавлены угловые скобки.
            Однако это объяснение не применимо к строкам! Одиночные кавыч-
         ки,  в которые заключаются строки,  не допускаются,  а добавление
         еще одного уровня приводит к путанице .  Если мы используем опера-
         тор define byte (определить байт) так:

            db        'dummy'             ;добавить элемент

         MASM разворачивает строку следующим образом:

            2         db     'dummy'      ;добавить элемент

         что может  создать  нам  довольно много значений dummy,  но не

                                      - 1-13 -
         сделает то,  что мы хотим.  Мы можем вызвать  использование  дей-
         ствительного аргумента через

            db        '&dummy'            ;добавить элемент

         но MASM развернет эту строку в

            2         db     "one"        ;добавить элемент

            Это приведет  к  появлению  специальной  ошибки "Чтение текста
         после конца". Такая же ошибка возникает, если Вы случайно создали
         бесконечный рекурсивный вызов макро.  В общем случае MASM выберет
         всю память для сохранения всех используемых символов. Будьте вни-
         мательны! Это сообщение об ошибке выдается до тех пор, пока Вы не
         прекратите работу MASM, нажав "Cоntrоl-C".

                           Резюме по использованию макро

            Из того , что мы изучили, видно - макросы используют некоторый
         тип "стенографического" программирования. Так, если вы определили
         некоторый блок программы,  Вы можете включать его многократно че-
         рез простой вызов макро.  Мы видели, что макросы определяются при
         помощи оператора MACRO,  который присваивает макро имя и дополни-
         тельно может снабжать его аргументами. Макроописание заканчивает-
         ся оператором ENDM.  После выполнения макроописания  вызов  макро
         осуществляется по его имени, за которым могут следовать некоторые
         параметры.
            Мы также видели,  как MASM,  используя директиву LOCAL,  может
         вырабатывать уникальные метки и как применяются директивы  повто-
         рения. Наши знания о директивах повторения и их использовании бу-
         дут расширены в следующей главе.
            Справочное Руководство  Программиста  для Операционной Системы
         MS-DOS фирмы Майкрософт содержит макроописания всех системных вы-
         зовов. Кроме того, в нем также содержатся некоторые основные мак-
         росы,  предназначенные для решения общих задач (например, переме-
         щение  строки).  Это  руководство  является  хорошим  пособием по
         применению макросов и их структурированию.  Ниже  приводятся  три
         таблицы,  которые наверняка окажутся Вам полезными.  В Табл.  1-1
         приводится свод макродиректив, используемых MASM. В Табл. 1-2 пе-
         речисляются  специальные  макрооператоры;  а в Табл.  1-3 собраны
         макродирективы управления листингом.
            Теперь мы  прошли полпути в изучении макросов для структуриро-
         ванных программ.  Для выполнения работы по созданию этих макросов
         нам необходимо знать, когда и что ассемблируется в программу. Это
         тема следующего раздела.

                             Условное  ассемблирование

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

            Когда может  потребоваться условное ассемблировние?  Предполо-
         жим,  что Вы пишите большую программу, которая, подобно большинс-
         тву таких программ,  имеет какие-то ошибки. Для выяснения обстоя-
         тельств работы программы Вы решаете  поместить  в  нее  некоторые
         отладочные операторы.  Однако , когда у Вас появится уверенность,
         что программа выполняется правильно,  Вы захотите изъять эти опе-
         раторы,  чтобы программа выполнялась без лишних кодов. Но так как
         программа, вероятно, содержит еще много ошибок, отладочные опера-
         торы  придется выполнять снова.  Добавление и удаление операторов
         утомительно.  Для решения этой проблемы может  быть  использовано
         условное ассемблирование. В Листинге 1-6 показано воздействие пе-
         реключателя "DEBUG" (отладить) на операторы  блока  при  условном
         ассемблировании.  Реализован  хороший способ редактирования прог-
         раммы, а переключатель .SALL использован для подавления некоторой
         части  макрорасширения  @TypeStr.  Наш  интерес  обращен только к
         строкам, связанным с условным ассемблированием.

                            Таблица 1-1. Макродирективы

            --------------------------------------------------------
            Директива      Переменная          Описание применения
            --------------------------------------------------------
            mname MACRO    parameter_list      МАКРООПИСAHИЕ
                                               Сигнализирует о нача-
                                               ле блока макроописа-
                                               ния; parameter_list
                                               определяет формальные
                                               аргументы, используе-
                                               мые в блоке.

            ENDM                               КОНЕЦ МАКРО
                                               Сигнализирует конец
                                               макроописания или бло-
                                               ка повторения REPT,IRP
                                               или IRPC. Наличие обя-
                                               зательно!

            EXITM                              ВЫХОД ИЗ МАКРО
                                               При достижении выход из
                                               макро. Наиболее часто
                                               используется при   ус-
                                               ловном ассемблировании.

            LOCAL      symbol_list             ЛОКАЛЬНЫЙ СИМВОЛ
                                               Определяет для ассембле-
                                               ра символы  из  списка
                                               symbol_list как уникаль-
                                               ные символы.
                                               Расширяется в ??ххх, где
                                               ххх - шестнадцатиричные
                                               числа.

            PURGE      macro_list              УДАЛИТЬ МАКРООПИСAHИЕ
                                               Уничтожает описание макро
                                               из списка macro_list.


                                      - 1-15 -
            --------------------------------------------------------
            Директива      Переменная          Описание применения
            --------------------------------------------------------
            REPT       выражение               ПОВТОРИТЬ
                                               Повторяет блок команд,
                                               размещенных между REPT
                                               и ENDM,столько раз,сколь-
                                               ко задано в выражении.

            ITP       dummy,<parameter_list>   НЕОГРAHИЧЕННОЕ ПОВТОРЕНИЕ

                                               Повторяет  блок команд
                                               между IRP  и  ENDM для
                                               каждого значения из para-
                                               meter_list,заменяя фор-
                                               мальный параметр (dummy)на
                                               значение параметра из каж-
                                               дого расширения.

            IRPC      dummy, строка             СИМВОЛ НЕОГРAHИЧЕННОГО
                                                     ПОВТОРЕНИЯ
                                               Повторяет блок команд
                                               между IRPC и  ENDM для
                                               каждого символа строки,
                                               заменяя формальный пара-
                                               метр на символ из каж-
                                               дого расширения.
            ---------------------------------------------------------

                 Таблица 1-2. Специальные символы в макрокомандах
            ---------------------------------------------------------
            Символ                      Описание применения
            ---------------------------------------------------------
            &argument         Соединяет формальные аргументы или сим-
                              волы с текстом. Особенно необходим для
                              подстановки вместо формальных аргументов
                              в строках, заключенных в кавычки.

            ;;comment text    Обозначает комментарий. Такой
                              комментарий никогда не приводится в
                              макроописании.

            !char             Указывает, что следующий далее символ
                              является литералом.   Используется  для
                              включения &, %  и т.д. в макрорасширения,
                              когда эти  символы могут быть интерпре-
                              тированы не как специальные.

            %symbol           Используется для преобразования символа
                              или выражения в число текущей   системы
                              счисления.
            <text>            Угловые скобки (< и >) используются для
                              определения текста,  заключенного между
                              ними, как литерала. Все, что  заключено
                              в такие скобки,  может  быть передано в
                              макро  как одиночный аргумент.
            ----------------------------------------------------------

                                      - 1-16 -


                   Таблица 1-3. Директивы управления листингом в
                                макрокомандах

            ----------------------------------------------------------
            Директива                Описание применения
            ----------------------------------------------------------

            .XALL        Приводит исходный и объектный код макрорасши-
                         рений, исключая исходные строки,  которые не
                         вырабатывают исполнительного кода.  Устанав-
                         ливается по умолчанию.

            .LALL        Приводит все строки макрорасширения, исключая
                         комментарии, начинающиеся с удвоенных точкой
                         с запятой /;;/.

            .SALL        Исключает код, вырабатываемый  макрорасшире-
                         нием.

            .LIST        Приводит строки исходного текста  программы.
                         Действие противоположно .XLIST,  однако сос-
                         тояние листинга, определяемое директивами
                         .XALL, .LALL или .SALL не изменяет.

            .XLIST        Подавляет  любую  выдачу.  Перекрывает  все
                          другие  директивы.
            ---------------------------------------------------------


             Листинг 1-6. Условное ассемблирование операторов DEBUG -
                                       FALSE
         ----------------------------------------------------------------
                                    ;часть А - листинг исходного текста
                     FALSE    EQU     0
                     TRUE     EQU     0FFFFh
                     DEBUG    EQU     FALSE
            .
            .
            .
                     @TypeStr 'hello world!'
                     IF       DEBUG        <--- начало условного блока
                     @TypeStr 'Hi -I made it to this point in the program'
                     ENDIF                 <--- конец условного блока
            .
            .
            .
                                    ;часть В - листинг MASM

                     @TypeStr 'hello world!'
            1                   mov     dx,offset  ??0000
            1                   mov     ax,09h
            1                   int     21h     ;вызвать функцию MS-DOS
                     ENDIF
         ----------------------------------------------------------------


                                      - 1-17 -

               Данный пример был ассемблирован со  значением переключателя
         DEBUG - FALSE (ложь).  В результате от условного блока в листинге
         MASM после расширения @TypeStr появляется только  оператор ENDIF.
         Таким образом MASM сообщает,  что условный блок присутствовал, но
         он не ассемблировался. Когда значение переключателя DEBUG изменя-
         ется на TRUE (истина), MASM вырабатывает другую программу, приве-
         денную в Листинге 1-7.


             Листинг 1-7. Условное ассемблирование операторов DEBUG -
                                       TRUE
         ------------------------------------------------------------------

                                     ;листинг MASM
                      DEBUG      EQU       TRUE
            .
            .
            .
                      @TypeStr  "hello world"
            1                   mov        dx,offset  ??0001
            1                   mov        ax,09h
            2                   int        21h   ;вызвать функцию MS-DOS
                      IF        DEBUG
                      @TypeStr  'Hi -I made it to this point in the program'
            1                   mov        dx,offset ??0002
            1                   mov        ah,09h
            2                   int        21h   ;вызвать функцию MS-DOS
                      ENDIF
         ----------------------------------------------------------------
            Все это время отладочные операторы присутствовали в программе.
         MASM также включает в листинг строку,  вызывающую ассемблирование
         операторов.  Если  Вы  предпочитаете увидеть в файле листинга все
         директивы условного ассемблирования вне зависимости от того, име-
         ют  они  значение  TRUE или FALSE,  используйте директиву .LFCOND
         (включить список ложных состояний).   Позднее Вы можете  подавить
         выдачу  ложных  состояний  при помощи директивы .SFCOND (подавить
         выдачу ложных состояний).  В основном, блок условного ассемблиро-
         вания  начинается  с  одной из разновидностей оператора IF (если)
         (см. полный список в Табл. 1-4) и заканчивается оператором ENDIF.
            Обычно использование  переключателей TRUE/FALSE в условном ас-
         семблировании возникает при программировании систем (программиро-
         вание операционных систем компьютеров). Если для Вашего компьюте-
         ра имеется исходный текст операционной системы на языке ассембле-
         ра, просмотрите его. Вы наверняка найдете, что условное ассембли-
         рование используется в нем весьма интенсивно.  Условное ассембли-
         рование   позволяет   проектировщику   писать  одну  операционную
         систему,  а через использование переключателей -  конфигурировать
         ее на конкретный набор аппаратных средств. Эти переключатели, по-
         добно переключателю DEBUG из нашего примера, позволяют сгенериро-
         вать работающую систему для данного типа, количества или конфигу-
         рации памяти, пульта, периферийных устройств, драйверов и т.д.
            Некоторые выражения,  которые  при вычислении дают 0 или имеют
         значение 0,  MASM рассматривает  как  FALSE.  Ненулевые  значения
         рассматривает  как  TRUE.  Обычно  для  символа TRUE используется
         шестнадцатиричное значение FFFFh. Это разрешает использовать TRUE
         в любых битовых операциях. Например, поразрядное  выполнение  AND

                                     - 1-18 -
         (логическое И) над 0001 и 1000 равно 0000,  так как хотя оба зна-
         чения и истинны,  их логическое произведение должно  быть  ложно.
         Вспомните, что MASM использует одни и те же операторы как для ло-
         гических, так и для битовых операций.


                 Таблица 1-4. Директивы условного ассемблирования

            ---------------------------------------------------------
            Директива       Переменная       Описание применения
            ---------------------------------------------------------

            IF              выражение     IF TRUE (если истина)
                                          Если значение  выражения не
                                          нулевое, операторы условно-
                                          го блока ассемблируются.

            IFE             выражение     IF FALSE (если ложь)
                                          Если значение выражения рав-
                                          но нулю, операторы условного
                                          блока ассемблируются.

            ELSE                          ELSE (иначе)
                                          Если значение условной ди-
                                          рективы ассемблирования рав-
                                          но FALSE (ложь) (условный
                                          блок не ассемблируется),
                                          ассемблируются альтернатив-
                                          ные операторы блока ELSE.
                                          Завершает блок IFXXXX, хотя
                                          после должно следовать ENDIF.
                                          Действительно только после
                                          оператора IFXXXX.

            ENDIF                         END блока IF (конец блока IF)
                                          Завершает блок IFхххх или
                                          ELSE.

            IF1,IF2                       IF MASM проход 1, IF MASM
                                          проход 2
                                          Ассемблирует условный блок,
                                          если MASM-ассемблер осущест-
                                          вляет указанный проход.
                                          См.взаимозависимость между IF1
                                          и IF2 и IFDEF и IFNDEF.

            IFDEF           символ        IF cимвол DEFINED (если сим-
                                          вол определен)
            IFNDEF          символ        IF символ NОT DEFINED (если
                                          символ не определен)
                                          Выясняет, определен ли сим-
                                          вол или он имеет внешнее
                                          объявление. IFNDEF противо-
                                          положно IFDEF. См. взаимо-
                                          связь с проходами ассемблера.

            IFB            <аргумент>     IF аргумент BLANK (если ар-
                                          гумент пуст).

                                      - 1-19 -
            ---------------------------------------------------------
            Директива       Переменная       Описание применения
            ---------------------------------------------------------
            IFNB           <аргумент>     IF аргумент NOT BLANK (если
                                          аргумент не пуст).
                                          Выясняет, пуст ли аргумент.
                                          Используется для определения
                                          передаваемых параметров мак-
                                          ро. IFNB противоположноIFB.
                                          Наличие угловых скобок обя-
                                          зательно.
            IFIDN       <str1>,<str2>     IF строка1 IDENTICAL TO
                                          строка2 (если строка1 иден-
                                          тична строке2)
            IFDIF       <strl>,<str2>     IF строка1 DIFFERENT FROM
                                          строка2 (если строка1 отлич-
                                          на от строки2)
                                          Определяет, идентичны ли
                                          строка1 и строка2. IFDIF
                                          противоположно IFIDN.
                                          Наличие угловых скобок обя-
                                          зательно.
            ---------------------------------------------------------

                                Операторы отношений

            Кроме использования  символов с предопределенными значениями и
         арифметических выражений MASM поддерживает  операторы  отношений,
         которые  могут  применяться  для управления операторами условного
         ассемблирования.  Операторами отношений являются операторы, выра-
         жающие  взаимосвязь  между  двумя  значениями.  Less than (меньше
         чем), greater than (больше чем), equal to (равно)  и not equal to
         (не равно) - примеры операторов отношений.
            Эти операторы позволяют выполнять специальные действия по про-
         верке на попадание в диапазон. Использование операторов отношений
         разрешает создавать довольно сложные программные структуры, кото-
         рые  автоматически настраиваются на конкретную среду функциониро-
         вания (например, определение размера области данных для запомина-
         ния   резервной   области  памяти).  Однако, используя  операторы
         отношений, MASM не всегда выполняет то, что планировалось.
            Работая с целыми со знаком, вы можете посчитать OFFFFh и -1 за
         одно  и  то же значение.  За некоторым исключением MASM также ис-
         пользует взаимозаменяемость значений. Хотя ранние версии MASM при
         работе  с отрицательными числами имели некоторые сложности, более
         новые версии (1.2 и выше) знают,  что -1 равно 0FFFFh, однако при
         сравнении  величин  двух  чисел MASM рассматривает их по-разному.
         Это иллюстрирует следующий пример:
            True       FFFF       dw    1 gt -1        очевидно
            False      0000       dw    1 gt 0FFFFh    65535, а не -1
            True       FFFF       dw   -1 ge 0FFFFh    -1=-1
            False      0000       dw   -1 gt 0FFFFh    -1 не больше -1

            В примере показано,  что MASM рассматривает 0FFFFh как положи-
         тельное целое число 65535,  однако при сравнении с -1 0FFFFh  ин-
         терпретируется как -1.  Об этом можно заметить:  "Кто предостере-
         жен, тот вооружен".
            Полный список операторов отношений приведен в Табл.  1-5. При-

                                      - 1-20 -
         мер использования  этих  операторов при программировании макрост-
         руктур помещен в конце данной главы.  В Табл. 1-6 приведен список
         директив условного ассемблирования.

              Таблица 1-5. Логические операторы и операторы отношений
                           условного ассемблирования
         ------------------------------------------------------------
            Оператор   Синтаксис            Описание применения
         ------------------------------------------------------------
            EQ        exp1 EQ exp2     TRUE,если выражение1 равно вы-
                                            ражению2
            NE        exp1 NE exp2     TRUE,если выражение1 не равно
                                            выражению2
            LT        exp1 LT ехр2     TRUE,если выражение1 меньше
                                            выражения2
            LE        exp1 LE exp2     TRUE,если выражение1 меньше
                                            или равно выражению 2
            GT        exp1 GT exp2     TRUE,если выражение1 больше
                                            выражения2
            GE        exp1 GE exp2     TRUE,если выражение1 больше
                                            или равно выражению2
            NOT       NOT  exp         TRUE,если выражение - FALSE,
                                            иначе FALSE
            AND       exp1 AND exp2    TRUE,если только оба выражение1
                                            и выражение2 - TRUE
            OR        exp1 OR  exp2    TRUE,если выражение1 либо выра-
                                            жение2 - TRUE
            XOR       exp1 XOR exp2    TRUE,если выражение1 равно лог.
                                            NOT от выражения2
            FALSE     (0000 16-CC)     Для IF TRUE любое нулевое выра-
                                       жение - FALSE
            TRUE      (FFFF 16-CC)     Для IF TRUE любoe ненулевое вы-
                                       ражение - TRUE
            ---------------------------------------------------------

              Таблица 1-6. Список директив условного ассемблирования

            ---------------------------------------------------------
            Директива                 Описание применения
            ---------------------------------------------------------
            .LFCOND         Приводит список ассемблерных условий, со-
                            ответствующих  FALSE.
            .SFCOND         Подавляет выдачу списка условий, соответ-
                            ствующих FALSE. По умолчанию устанавлива-
                            ется .SFCOND.
            .TFCOND         Включает список условного ассемблирования
                            FALSE аналогично переключателю MASM /X.
                            Действует независимо от переключателей
                            .LFCOND и .SFCOND.
            .LIST           Приводит список исходных строк. Противо-
                            положно .ХLIST, но не изменяет характерис-
                            тик листинга условного ассемблирования,
                            определенных .LFCOND,.SFCOND или .TFCOND.
            .XLIST          Подавляет любую выдачу.  Перекрывает все
                            предыдущие  директивы.
         ------------------------------------------------------------

                                      - 1-21 -

                         Условное ассемблирование. Выводы

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

                        Условное ассемблирование и МАКРОСЫ

            Хотя условное  ассемблирование часто используется с определен-
         ными явно ключами,  основной потенциал условного  ассемблирования
         реализуется при его сочетании с возможностью макрокаманд. Сущест-
         вует целый набор возможностей условного  ассемблирования, которые
         специально  ориентированы на работу с макрокомандами.  Рассмотрим
         основные из этих возможностей.
            Макросы могут  быть  разделены на две группы.  В первую группу
         входят макросы,  ориентированные на создание определенных  струк-
         тур,  зависящих от некоторых входных данных, причем эти структуры
         хорошо определены, а входные данные принадлежат конкретному клас-
         су.  Примером может служить макро file_head,  предназначенное для
         ввода блока определения файла.
            Вторая группа макро предназначена для генерации структур,  за-
         висящих от информации, доступной программисту, или такой информа-
         ции,  которую программист считает несущественной и подлежащей иг-
         норированию.  Эти  макросы  часто   должны   уметь   обрабатывать
         множество классов аргументов и определять класс аргументов.  В то
         же время эти макросы  могут  поддерживать  локальные  данные  или
         счетчики,  освобождая  программиста  от рутинной работы.  Макросы
         структурного управления,  приводимые в  оставшейся  части  данной
         главы, являются первыми примерами второй группы. Конечно, макросы
         этих двух групп обычно частично перекрываются.
            Для одного  типа макро программист использует эти средства для
         того,  чтобы избежать дополнительного ввода данных  с  клавиатуры
         или  какой-либо  другой  неблагодарной  работы.  Для другого типа
         программист использует эти средства для создания структур высоко-
         го уровня, основанных на способности ассемблера поддерживать про-
         пущенную информацию.  В целях упрощения своей работы  программист
         умышленно прячет детали реализации.
            Примером макро высокого уровня является  использование  макро,
         упрощающих применение мнемоник в ассемблере. Хотя большинство ко-
         манд процессора 8086 может использоваться с регистровыми  операн-
         дами или операндами памяти, многие из них не позволяют непосредс-
         твенных операндов. Примером является команда PUSH, хотя  186/188

                                      - 1-22 -

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

                            Определение типов операндов

            В среде  8086/8088  существует четыре основных типа операндов.
         Это - регистровый операнд,  непосредственный операнд, операнд па-
         мяти и адреса. Для операндов, ориентированных на данные, возможны
         подтипы.  Регистры представляют собой специальные случаи суммато-
         ров  (регистр общего назначения A) и регистры сегментов.  Все три
         типа данных могут подразделяться на 8- и 16-битовые данные. Адре-
         са  могут  быть near (близкие,  состоящие только из смещения) или
         far (далекие, состоящие из смещения и сегмента).
            Как мы  будем  различать  все эти типы?  Мы будем использовать
         операторы MASM .TYPE и TYPE. В Табл. 1-7 приведены результаты ис-
         пользования этих операторов с различными классами операндов.

                     Таблица 1-7.  Операторы MASM .TYPE и TYPE

                             Правила для .TYPE  и TYPE
            ---------------------------------------------------------
            Оператор                           Результат
            ---------------------------------------------------------
            .TYPE  биты 5 и 7           8х     Определено внешне
                                        2х     Определено локально
                                        0х     Неверная ссылка
            .TYPE биты 0 ... 2          х0     Абсолютный режим
                                        х1     Программозависимый
                                        х2     Зависимый по данным
            TYPE  с переменной          01     Переменная в байт
                                        02     Переменная в слово
                                        04     Переменная в двойное
                                               слово
                                        08     Переменная в четверное
                                               слово
                                        10     Переменная в 10 байтов
                                        ХХ     Структура размерностью
                                               в ХХ
            TYPE с программной меткой   FFFF   "Близкая" программная
                                               метка
                                        FFFE   "Далекая" программная
                                               метка
         -------------------------------------------------------------

                                      - 1-23 -

                             Примеры для .TYPE и TYPE
         -------------------------------------------------------------
            Тип переменной     .TYPE   Определено  TYPE  Определено
         -------------------------------------------------------------
            Непосредственный     20    локально      0   неверно
            Регистровый          20    локально      0   неверно
            Метка данных         22    локально      Х   количество
                                                         байтов
            "Близкая" метка      21    локально     FFFF "близкая"
                                                         метка
            "Далекая" метка      21    локально     FFFE "далекая"
                                                         метка
            Код операции MASM    00    неверно       0   неверно
            Нонсенс              00    неверно       0   неверно
         -------------------------------------------------------------


            Список примеров можно продолжить. Хотя .TYPE и распознает име-
         на различных регистров,  он не распознает регистровые конструкции
         типа [BX] или ARRAY[BX][SI]. Односимвольная константа типа A рас-
         познается оператором .TYPE как переменная, определенная локально.
            Во время  первого  прохода  ассемблера  ссылка вперед никак не
         распознается.  IFDEF в качестве результата возвращает "не опреде-
         ленно",  .TYPE  возвращает  "неверно",  а TYPE возвращает нулевую
         длину. К ссылкам вперед может быть применено только одно правило:
         если возможно, не применять их вообще.

                    Фазовые ошибки и некоторые особенности MASM

            С использованием  операторов  MASM связана одна важная особен-
         ность.  MASM является двухпроходным ассемблером, назначающим зна-
         чения  символам на первом проходе и затем вычисляющим символы при
         втором проходе.  Программные метки и метки данных являются симво-
         лами.  Их значения определяются во время первого прохода, а затем
         используются на втором проходе для генерации программного кода.
            Рассмотрим следующее дерево событий.  При  обнаружении  ссылки
         вперед  MASM  не распознает метку на первом проходе и не способен
         определить ее тип. Попытка ссылки на этот символ вызывает появле-
         ние сообщения об ошибке "Символ не определен".  MASM обнаруживает
         эту ошибку на первом проходе,  но подавляет ее и  продолжает  ас-
         семблирование.  MASM  способен  подавить ошибку,  предположив тип
         символа из контекста, в котором он появился. Если это предположе-
         ние  неверно,  MASM может закончить свою работу,  выдав сообщение
         "фазовая ошибка между проходами",  или может укоротить команду  и
         поместить после нее команду nop (нет операции),  как заполнитель.
            Существует два способа устранения фазовых ошибок при  нормаль-
         ной  работе MASM.  В большинстве случаев MASM способен определить
         тип операндов из контекста. Программисты редко применяют переходы
         в  сегменте данных и обычно не добавляют программных адресов. Для
         тех специальных случаев,  когда MASM делает ошибочные предположе-
         ния,  программист может определить порядок работы ассемблера, ис-
         пользуя оператор перекрытия PTR (указатель).  С помощью оператора

                                     - 1-24  -
         PTR программист может явно указать тип ссылки вперед, и, следова-
         тельно, MASM не сделает ошибочного предположения. Однако, пытаясь
         выработать многоцелевые команды макро, мы значительно увеличиваем
         вероятность ошибочного выражения.  Если наши многоцелевые команды
         предназначены для обработки любых классов операндов,  точное зна-
         чение  такого класса трудно определить из контекста.  Кроме того,
         хотя использование PTR и может помочь в некоторых подобных случа-
         ях  (как мы увидим в макро @PushOP),  оно не достигает цели осво-
         бождения программиста от излишних деталей.
            Проанализировав то,  как ошибочное предположение  вырабатывает
         фазовую ошибку,  мы с большей вероятностью можем устранить ее по-
         явление.  Так как фазовые ошибки являются результатом смены неко-
         торыми символами (например,  меток) своих значений между прохода-
         ми,  важно,  чтобы макросы  вырабатывали  один  и  тот  же  объем
         программного  кода  на каждом проходе.  Это предохраняет значения
         меток, размещенных после макро, и объясняет, почему MASM забивает
         укороченные команды инструкциями NOP. От одного прохода к другому
         должны также оставаться постоянными программные метки.

                              Сравнение строк. Пример

            К сожалению,  способность оператора .TYPE распознавать  непос-
         редственные операнды и регистры значительно уменьшается при выяс-
         нении типа операндов макро. Так как особенно важно знать, являет-
         ся ли аргумент макро регистром,  мы должны сконструировать способ
         выявления этого.  Определение того,  является ли аргумент регист-
         ром,  обычно полезно только вместе с неявным предположением,  что
         если он не является регистром и не является определенной адресной
         ссылкой, то предположительно является ссылкой на непосредственные
         данные.
            Это хорошо сочетается с использованием условного ассемблирова-
         ния  на базе директив IRP и IRPC.  Целью в данном случае является
         определение принадлежности аргумента макро к  какому-либо набору.
         Для решения задачи - является ли аргумент регистром, используется
         сопоставление строк.  Так как  оператор  .TYPE  может  определить
         только то,  что эти регистры объявлены локально и абсолютны,  для
         явной проверки имени  регистра  применяется  макро  сопоставления
         строк. Эту  функцию  выполняет  приведенное  в Листинге 1-8 макро
         ?reg.


              Листинг 1-8. Макро сопоставления имен регистров - ?reg
         -----------------------------------------------------------------

            FALSE    EQU     0
            TRUE     EQU     0FFFFh
            ;;
            ;;**** ?REG - Определить, является ли аргумент регистром
            ;;
            ?reg     MACRO   arg
            ?isr8    =       FALSE
            ?isr16   =       FALSE
                     IRP     reg,<ax,bx,cx,dx,bp,sp,si,di,cs,ds,es,ss>
                     IFIDN   <&&reg>,<&arg>
                     ?isr16  =       TRUE
                     EXITM

                                      - 1-25 -
                     ENDIF
                     ENDM    ;; конец секции IRP
            ;; Если сравнились, остановиться здесь
                     IF      (?isr16)
                     EXITM
                     ENDIF
            ;; Если еще не сравнились, продолжить далее
                     IRP     reg,<ah,bh,ch,dh,al,bl,cl,dl>
                     IFIDN   <&&reg>,<&arg>
                     ?isr8   =        TRUE
                     EXITM
                     ENDIF
                     ENDM    ;; конец секции  IRP
            ;; Если сравнились, остановиться здесь
                     IF      (?isr8)
                     EXITM
                     ENDIF
            ;; Если не сравнились, попробовать прописные имена регистров
                     IRP     reg,<AX,BX,CX,DX,BP,SP,SI,DI,CS,DS,ES,SS>
                     IFIDN   <&&reg>,<&arg>
                     ?sir16  =       TRUE
                     EXITM
                     ENDIF
                     ENDM    ;; конец секции IRP
            ;; Если сравнились, остановиться здесь
                     IF      (?isr16)
                     EXITM
                     ENDIF
            ;; Если еще не сравнились, попробовать еще
                     IRP     reg,<AH,BH,CH,DH,AL,BL,CL,DL>
                     IFIDN   <&&reg>,<&arg>
                     ?isr8   =        TRUE
                     EXITM
                     ENDIF
                     ENDM    ;; конец секции IRP
                     ENDM    ;; конец макроописания
         -----------------------------------------------------------------

            Cердцевиной этого макро, как и любого макро сопоставления, яв-
         ляются команды:

            IRP       reg,<ax,bx,cx,dx,bp,sp,si,di,cs,ds,es,ss>
            IFIDN     <&&reg>,<&arg1>
            ?isr16    %        TRUE

            Интерпретировать эти строки можно так:

            Для reg равного ax...ss выполнить . . .
                Если reg равно аргументу arg . . .
                     Аргумент есть регистр!

            Здесь следует  остановиться  на  двух   интересных   моментах.
         Во-первых,  необходимо явно проверять имена регистров, написанные
         как малыми, так и большими буквами. Директива условного ассембли-
         рования  IFIDN сравнивает строки на точное соответствие. Несмотря
         на все усилия, макро ?reg не полно. Оно не сопоставляет имена ре-
         гистров, состоящие из одной большой и одной малой буквы (например

                                      - 1-26 -
         "aL"). Во-вторых,  необходимо  проводить  две  проверки: одну для
         16-битовых регистров и одну для 8-битовых регистров. В данной ре-
         ализации наличие двух отдельных проверок не приносит  нам никакой
         выгоды, однако такие проверки окажутся полезными в следующем при-
         мере.
            Макро ?reg  имеет  два дополнительных синтаксических элемента.
         Один - директива завершения макро EXITM.  Эта директива использу-
         ется для завершения работы макро ?reg при обнаружении совпадения.
            Менее очевидно использование двойного амперсанда  в  операторе
         IFIDN. Согласно Руководству по MASM фирмы Майкрософт пользователь
         должен "указывать столько амперсандов,  сколько  имеется  уровней
         вложенности".  Столь лаконичное выражение не вносит ясности в ре-
         шение проблемы.  "Уровни вложенности" относятся не к глубине бло-
         ков,  где появляется ссылка, а к глубине блоков, где находится ее
         описание. Таким образом, arg1 приводится только с одним амперсан-
         дом, в то время как reg, описание которого находится во вложенном
         блоке,  требует наличия двух амперсандов. Фирма Майкрософт не ут-
         верждает,  что это предел разрешенного количества уровней вложен-
         ности или количества требуемых амперсандов.  В тех случаях, когда
         казалось  бы необходимо указывать множество амперсандов,  попытки
         написания примеров, позволяющих выявить правильное функционирова-
         ние, не увенчались успехом.
            Приведенный Листинг 1-9 с макро ?reg показывает, что это макро
         выполняет  возложенную на него функцию.  Заметьте,что регистр bР,
         который распознается MASM,  отбрасывается макро ?reg.  Это  может
         быть  истолковано,  как необходимость соблюдения строгого правила
         ввода исходного текста программы с клавиатуры.


              Листинг 1-9. Тест макро сравнения имен регистров - ?reg
         -----------------------------------------------------------------

                       ?reg   ax      ; "AX" - регистр ?
            FFFF       dw     ?isr16               <--- TRUE
                       ?reg   CS      ; "CS" - регистр ?
            FFFF       dw     ?isr16               <--- TRUE
                       ?reg   zork    ; "ZORK" - регистр ?
            0000       dw     ?isr16               <--- FALSE
            0000       dw     ?isr8                <--- FALSE
                       ?reg   01234h  ; "1234" - регистр ?
            0000       dw     ?isr16               <--- FALSE
            0000       dw     ?isr8                <--- FALSE
                       ?reg   bР      ; "BP" - регистр ?
            0000       dw     ?isr16               <--- FALSE
            0000       dw     ?isr8                <--- FALSE
         -----------------------------------------------------------------

                      Синтаксический анализ аргументов макро

            С помощью макро, распознающего имена регистров, можно реализо-
         вать обобщенное макро PUSH,  которое мы назовем @PUSHOP (протолк-
         нуть операнд).  (Замечание: мы рассматриваем имя pusha для " про-
         толкнуть все", но PUSHA является кодом операции для чипов 186,188
         и 286 фирмы Intel.  Использование его для макро может  ограничить
         совместимость снизу вверх. Конечно, Вы всегда можете использовать
         команду PUSHA в макро pusha для микропроцессоров 8086 и 8088).
            Как упоминалось  ранее относительно типа операнда,  который не

                                      - 1-27 -
         определен и не является регистром,  необходимо  делать  некоторые
         предположения.  Для  макро  @PUSHOP предположим,  что неизвестные
         операнды являются ссылками на  непосредственные  данные.  @PUSHOP
         ссылается  на  макро  ?reg,  и  макро ?reg должно быть включено в
         программу с @PUSHOР. Макро @PUSHOP см. в Листинге 1-10.
            @PUSHOP использует возможность макро ?reg различать 16-  и  8-
         битовые регистры. Так как команда PUSH не обрабатывает  8-битовый
         регистр, для получения первого символа имени регистра использует-
         ся директива IRPC. Затем @PUSHOP добавляет х, формируя ,таким об-
         разом,  имя 16-битового регистра, которое приемлемо для PUSH. За-
         метим,  что  в  этом  операторе  снова  необходимо  использование
         удвоенного амперсанда, причем с обеих сторон формального аргумен-
         та, так как сцепление строк возникает с обоих концов.
            Для тех случаев, когда предполагается наличие непосредственных
         данных,  вызывается макро @PushIm.  Это макро более сложное , чем
         минимально необходимое,  так как предполагается, что для передачи
         непосредственных данных  в  стек  нельзя  использовать  регистры.
         Вместо этого макро использует указатель базы (BP) на адрес стека.
         После сохранения BP и AX в стеке @PushIm заносит непосредственные
         данные  поверх содержимого AX,  обменивая их со старым содержимым
         BP.  После восстановления содержимого BP макро извлекает содержи-
         мое AX,  выталкивая его из стека.  Макро @PushIm приведено в Лис-
         тинге 1-11.

                   Листинг 1-10. Обобщение макро PUSH - @PushOp
         ----------------------------------------------------------------

            ;; **** @PushOp макро с обобщенным операндом команды PUSH
            ;; Если операнд определен, он может быть:
            ;;              регистром
            ;;              ссылкой на данные
            ;;
            ;; Если операнд не определен, он полагается ссылкой на
            ;;              непосредственные данные
            @PushOp  Macro  arg
                     .SALL
                     IFDEF  &arg                 ;; операнд определен ...
                        ?argtyp = .type &arg           ;; выявить его тип
                        IF     ((?argtyp and 3) EQ 2)  ;;операнд - данные
                          ?argsiz = ((type &arg) + 1)/2 ;; получить длину
                                                        ;; в словах
                          ?argoff = 0          ;; установить смещение в 0
                          REPT   ?argsiz   ;; повторить для каждого слова
                             ?argadd = word ptr &arg + ?argoff ;;получить
                             .XALL                               тип ptr
                             push     ?argadd ;;продв-ть непоср.в память
                             .SALL
                             ?argoff = ?argoff + 2 ;;след-ее слово данных
                           ENDM
                         ENDIF
                         IF ((?argtyp AND 3) EQ 1)  ;;операнд - программа
                           @PushImOff   &arg  ;;продвинуть смещение метки
                         ENDIF
                         IFE    (?argtyp and 3) ;;операнд - абс.значение
                           ?reg &arg
                           IF   (?isr16)        ;;операнд - регистр 16
                             .XALL

                                      - 1-28 -
                             push        &arg   ;;продвинуть непосред.
                             .SALL
                          ELSE
                            IF  (?isr8)         ;;операнд - регистр 8
                              IRPC chr1,&arg1
                                .XALL
                                push  &&chr1&&x ;;сохранить короткий рег.
                                .SALL
                                EXITM
                                ENDM
е                             ELSE            ;;предположить непосред.данны
                                @PushIm &arg  ;;продвинуть непосред. данные
                              ENDIF
                            ENDIF
                          ENDIF
                        ELSE                  ;;продвинуть непосред.данные
                           @PushIm &arg
                        ENDIF
                        ENDM                  ;;конец макроописания
         -----------------------------------------------------------------


            Листинг 1-11. Макро проталкивания непосредственных данных -
                                      @PushIm
         ----------------------------------------------------------------

            ;; **** @PushIm макро проталкивания непосредственных данных
            @PushIm Macro arg
                    .XALL
                    push   bp       ;;сохранить указатель базы
                    mov    bp,sp    ;;переместить указатель стека в BP
                    push   ax       ;;сохранить накопитель
                    mov    ax,&arg  ;;получить непосредственные данные
                    xchg   [bp],ax  ;;обменять старый BP и непосред. данные
                    mov    bp,ax    ;;восстановить старый BP из AX
                    pop    ax       ;;восстановить накопитель
                    .SALL
                    ENDM            ;;конец макроописания
         ----------------------------------------------------------------

            Эта операция свертывания может быть сведена к обмену элемента-
         ми  внутри  стека.  Однако ведение подобных игр со стеком опасно.
         Если Ваш компьютер поддерживает прерывания, для предохранения це-
         лостности данных стека эта операция должна выполняться только че-
         рез запрещение прерываний.
            Для тех  случаев,  когда  делается  попытка протолкнуть в стек
         программные адреса,  мы предполагаем, что программист желает сох-
         ранить  действительное  смещение метки.  Для проталкивания в стек
         смещения метки,  как непосредственных данных,  было  использовано
         макро @PushImOff.  Оно отличается от макро @PushIm только исполь-
         зованием команды:

             mov      ax,offset  &arg   ,

         что противоположно простому перемещению в  макро  @PushIm.  Макро
         @PushImOff приведено в Листинге 1-12.


                                      - 1-29 -

                Листинг 1-12.  Макро  продвижения  в стек смещения
                       непосредственных данных - @PushImOff
         ---------------------------------------------------------------

            ;; **** @PushImOff макро продвижения смещения непосред.данных
            @PushImOff  MACRO  arg
                   .XALL
                   рush    bp       ;;сохранить указатель базы
                   mov     bp,sp    ;;переместить указатель стека в BP
                   push    ax       ;;сохранить накопитель
                   mov     ax,offset &arg ;;получить смещение
                                          ;;непосред.данных
                   xchg    [bp],ax  ;;обменять старый BP и непоср.данные
                   mov     bp,ax    ;;восстановить старый  BP из AX
                   pop     ax       ;;восстановить накопитель
                   .SALL
                   ENDM             ;;конец макроописания
         ----------------------------------------------------------------


            Последним дискретным случаем,  который распознает @PushOp, яв-
         ляется попытка прямого проталкивания в стек данных памяти.  Слож-
         ность  в  этом  случае  заключается в том,  что стек воспринимает
         только 16-битовые данные.  Используя  директиву  перекрытия  PTR,
         можно убедить MASM сохранять нужные данные пословно.  @PushOp со-
         держит цикл,  который повторяет эту операцию  для  каждого  слова
         сохраняемых  данных,  увеличивая  адрес на два при каждом проходе
         цикла.  Таким образом можно сохранять в стеке двойные слова, чет-
         верные слова, 10-байтовые данные и структуры данных.
            Наконец, заметим,  что макро @PushOp все еще  не  обрабатывает
         ссылки, содержащие сложную адресацию (2[BP] и т.д.). В случае не-
         обходимости Вы можете применить подобные проверки, используя мак-
         родирективу  IRPC  для  выявления в аргументах квадратных скобок,
         адресации "база + индекс" и "база + смещение".
            Итоговый тест  макро @PushOp приведен в Листинге 1-13, который
         содержит результат нескольких вызовов макро @PushOp.
            Последняя операция листинга,  где @PushOр обрабатывает 4-слов-
         ную переменную,  не может быть изъята. Каждое проталкивание имеет
         один  и  тот  же аргумент.  Однако из этого красивого листинга не
         видно,  что каждая его строка имеет перемещаемый адрес (0000  для
         первого слова, 0002 для второго и т.д.). К сожалению, мы не можем
         в данной книге привести 132-колоночные листинги, и, если Вы хоти-
         те проверить, попробуйте получить такой листинг сами.
            Этот пример особенно полезен тем,  что демонстрирует одну  об-
         ласть,  где применение макро почти всегда предпочтительнее приме-
         нения подпрограмм.  Что касается обработки стека (как в @PushIm и
         @PushImOff),  макросы  способны выполнять эти операции без "угро-
         зы" влияния команды CALL на стек.  Это особенно важно при переме-
         щении или изъятии данных из стека,  так как подпрограмма не может
         изменить вершину стека и осуществить возврат, не вызвав серьезных
         осложнений.

                             Некоторые предупреждения
                    по использованию условного  ассемблирования
                                 и макросов в MASM

            Применяя макросы,  мы старались забыть,  что они  вырабатывают

                                      - 1-30 -
         построчный  код  и не вызывают программ.  Хотя это и представляет
         некоторые преимущества при быстрой генерации программного  кода и
         освобождает нас от ограничений по использованию стека,  результа-
         том линейной программы является более объемный код.  Как програм-
         мист, Вы должны решить, когда целесообразно вызывать быстро рабо-
         тающее макро,  а когда  -  подпрограмму,  экономящую  память,  но
         имеющую разветвленную структуру. В общем случае  используйте мак-
         ро, когда программный код невелик, а время критично, или когда Вы
         хотите конфигурировать программу, настроив ее на конкретные усло-
         вия.  Используйте подпрограмму, когда программный код велик и его
         нужно расположить в одном месте (так,  чтобы его можно было легко
         изменить).
            Другим тонким  моментом  работы  макро  является использование
         символов.  Вы помните, что символы определяются при помощи опера-
         тора  equ или =.  Затем эти символы вычисляются MASM и заменяются
         их значениями.  Иногда случается,  что программист забывает,  что
         аргументы макро не являются символами и наоборот.  Согласно Руко-
         водству по MASM аргументы макро заменяются действительными  пара-
         метрами  с использованием подстановки "один к одному".  Аргументы
         макро могут создаваться в одном макро  и,  используя  возможность
         текстовой подстановки, передаваться как составная строка текста в
         другое макро.  Это невозможно с символами.  Символам  может  быть
         присвоено  текстовое  значение  при помощи оператора equ,  что не
         позволяет модифицировать их впоследствии.  Только оператор = раз-
         решает  присваивать символам цифровые значения или атрибуты TYPE.
         Пример такого ограничения и один из способов его обхода представ-
         лен при рассмотрении операторов структурного управления.

             Листинг 1-13. Пример расширения обобщенного макро @PushOp
         -----------------------------------------------------------------
            dat_seg SEGMENT
            datq    dq      4040414142424343h
            dat_seg ENDS
                    .
                    .
                    .
            start:
                    @PushOp ax          ;сохранение общего регистра
            1               push   ax
                    @PushOp cs          ;сохранение регистра сегмента
            1               push   cs
                    @PuchOp al          ;сохранение короткого регистра
            2               push   ax...     ;сделать общий регистр
                    @PushOp 01234h      ;сохранить константу
            2               push   bp
            2               mov    bp,sp
            2               push   ax
            2               mov    ax,01234h
            2               xchg   [bp],ax
            2               mov    bp,ax
            2               pop    ax
                    @PushOp  'A'         ;сохранение константы
            2                push   bp
            2                mov    bp,sp
            2                push   ax

                                      - 1-31 -
            2                mov    ax,'A'
            2                xchg   [bp],ax
            2                mov    bp,ax
            2                pop    ax
                   @PushOp   start     ;сохранить смещение програм.метки
            2                push   bp
            2                mov    bp,sp
            2                push   ax
            2                mov    ax,offset start
            2                xchg   [bp],ax
            2                mov    bp,ax
            2                pop    ax
                   @PushOp   datq       ;сохранить четверную переменную
            2                push   ?argadd         ;1-ое слово
            2                push   ?argadd         ;2-ое слово
            2                push   ?argadd         ;3-ое слово
            2                push   ?argadd         ;4-ое слово
                   .
                   .
                   .
         ----------------------------------------------------------------

                Структурные операторы управления в языке Ассемблер

            Теперь, когда мы имеем все необходимые средства для построения
         структурных операторов управления,  разрешите приступить к этому.
         Наиболее  часто  употребимые  операторы  структурного  управления
         представлены в Табл. 1-8.
            Операторы Табл. 1-8 наиболее часто используются для реализации
         структурного управления в структурированных программах. В некото-
         рых языках их больше;  в других -  меньше.  Заметим  только,  что
         ФОРТРAH  добился  использования  структуры IF-THEN-ELSE в ФОРТРА-
         НЕ-77. Почти все ассемблеры не имеют таких структур для целей ко-
         дирования,  хотя  многие из них поддерживают IF-THEN-ELSE для ус-
         ловного  ассемблирования.   Причина   проста:   полагается,   что
         ассемблеры  находятся  на  более низком уровне чем языки высокого
         уровня.  Так как мы решили,  что эти структуры могут  значительно
         упростить программирование,  и мы можем реализовать их, используя
         средства, рассмотренные ранее.
            Мы упустили  из рассмотрения одну конструкцию.  Это - оператор
         CASE (выбор). Конструкция, которую мы представим, заимствована из
         синтаксиса языка Паскаль,  однако аналогичные конструкции имеются
         в Си и других языках программирования. Задача оператора CASE зак-
         лючается в проверке на равенство значения ключевой переменной var
         элементу из списка.  Если исходный оператор и варианты из  списка
         не содержатся в одном и том же макро,  невозможно определить, что
         является ключевой переменной. Вспомним, что MASM не разрешает ис-
         пользовать строки с (=) оператором назначения символа.
            Создать оператор CASE можно, перечисляя для него все вероятные
         случаи  и  соответствующие  им метки в качестве аргументов одного
         макро. Это псевдомакро рассматривается в следующем разделе данной
         главы.
            Полный список описаний структурированных макросов  управления
         приведен в Листинге 1-14. Обратите внимание на обильное использо-
         вание комментариев макро (;;), экономящих память  области  макро.
         Эти макросы вырабатывают множество символов.Они могут использова-
         ться в любом допустимом порядке с теоретическим ограничением уро-

                                      - 1-32 -
         вней вложенности до 89. Однако MASM скорее выходит за пределы па-
         мяти,  чем за предел вложенности. Никакая инициализация не требу-
         ется. Все символы самоинициализирующиеся.


                  Таблица 1-8. Операторы структурного управления

            --------------------------------------------------------
            Оператор                         Структура
            --------------------------------------------------------
            IF-THEN           IF<условие> (выполнить, если условие
                              истинно)
                              ENDIF
            IF-THEN-ELSE      IF<условие> (выполнить, если условие ис-
                              тинно)
                              ELSE (выполнить, если условие ложно)
                              ENDIF
            DO-WHILE          WHILE <условие> (выполнить, если усло-
                              вие истинно)
                              END_WHILE
            REPEAT-UNTIL      REPEAT (выполнить, если условие ложно)
                              UNTIL <условие>
            FOR-DO            FOR<var> = <begin> to <end> (выполнить
                              для каждого целого значения var между
                              begin и  end  включительно,  увеличивая
                              или уменьшая var при каждом прохождении
                              цикла)
                              END_FOR
            CASE_OF_<var>     CASE <var> OF
                              <case A> (выполнить, если var = A)
                              <case B> (выполнить, если var = B)
                              .  .  .
                              <case N> (выполнить, если var = N)
                              <по умолчанию> (выполнить, если нет со-
                              ответствия)
                              END_CASE


                Листинг 1-14. Структурированные макросы управления
         -----------------------------------------------------------------

            PAGE    50,132   ;установить выдачу листинга на полный экран
            ;;********************************************************
            ;;                М А К Р О О П И С А Н И Я
            ;;********************************************************
            ;;
            FALSE   EQU     0          ;определить "FALSE"
            TRUE    EQU     0FFFFh     ;определить "TRUE"
            ;;
            ;;** @TestSym *************************SUPPORT MACRO******
            ;; Выявить, был ли определен уровень вложенности. Если нет,
            ;; то установить "?SYMDEF" для инициализации счетчика этого
            ;; уровня. Обычно все процессы первого прохода запускают
            ;; счетчик с 0. Значения всех символов в начале прохода 2
            ;; должны быть сброшены. Заметьте, что символы "?p2sw..."
            ;; остаются для "Phase 2 SWitch" (переключатели фазы 2).
            ;; Проверьте, что первым уровнем при реинициализации явля-

                                      - 1-33 -
            ;; ется уровень 10. Замечание: значение 10 выбрано для пер-
            ;; воначального уровня с тем, чтобы зарезервировать для
            ;; уровня вложенности   две цифры.
            ;;
            @TestSym MACRO p1,p2
                    IF1       ;;если 1-ый проход,проверить что определено
                    IFNDEF    &p1&p2
            ?p2sw&p1&p2      = TRUE    ;;установить для 2-го прохода
                                       ;;переопределение переключателей,
            ?symdef          = FALSE   ;;вызывающее инициал-ию счетчика
                     ELSE
            ?symdef          = true    ;;разрешить приращение счетчика
                     ENDIF   ;;конец проверки описания символа
                     ENDIF   ;;конец проверки 1-го прохода
                     ;;
                     IF2     ;;если 2-ой проход, переинициал-ать
                     IF      (?p2sw&p1&p2) ;;если переинициал. нет, то
            ?p2sw&p1&p2      = FALSE   ;;очистить переключатель переопре-
                                       ;;деления 2-го прохода
                     IF      (?p2sw&р1&10) ;; и проверить инициал-ию
                                           ;;уровня 10
                     .ERR    ;; выйти с сообщением об ошибке
                     %OUT @TestSym macro: &p1 уровень вложенности не
                     %OUT                 закрыт на 2-ом проходе
                     ENDIF   ;;закон. уровень 10 по проверке инициал-ции
            ?symdef          = FALSE   ;;вызвать переинициал-ию счетчика
                     ELSE
            ?symdef          = TRUE    ;;разрешить приращение счетчика
                     ENDIF   ;;закончить проверку "если не переинициал."
                     ENDIF   ;;закончить проверку прохода 2
                     ENDM    ;;закончить макроописание
            ;;
            ;;**@ZeroSym**************************SUPPORT MACRO********
            ;; Инициализировать счетчик последовательности вложенностей
            ;; при первом использовании
            @ZeroSym         MACRO     p1,p2
            &p1&p2 =        0
                   ENDM
            ;;
            ;; ** @IncSym *********************** SUPPORT MACRO ******
            ;; Увеличить на 1 счетчик последовательности вложенностей
            @IncSym          MACRO    p1,p2
            &p1&p2 =                  &p1&p2   + 1
                   ENDM
            ;;
            ;; ** @DecSym *********************** SUPPORT MACRO *****
            ;; Уменьшить на 1 счетчик последовательности вложенностей
            @DecSym          MACRO    p1,p2
            &p1&p2 =                  &p1&p2   -1
                   ENDM
            ;;
            ;; ** @MakeJmp2 ********************** SUPPORT MACRO *****
            ;; Вставить команду перехода (JMP) в программный код
            @MakeJmp2        MACRO    p1,p2,p3
                    jmp               &p1&p2&p3
                   ENDM

                                      - 1-34 -
            ;;
            ;; ** @MakeJmp *********************** SUPPORT MACRO *****
            ;; Переформатировать символы для команды JMP
            @MakeJmp         MACRO    p1,p2,p3
            ??tmp  =                  &p3&p2
                   @MakeJmp2          p1,p2,%??tmp
                   ENDM
            ;;
            ;; ** @MakeJmpLabel2 *****************SUPPORT MMACRO *****
            ;; Вставить метку команды JMP в программный код
            @MakeJmpLabel2   MACRO    p1,p2,p3
            &p1&p2&p3:
                   ENDM
            ;;
            ;; ** @MakeJmpLabel ****************** SUPPORT MACRO *****
            ;; Переформатировать символы для определения метки опера-
            ;; тора JMP
            @MakeJmpLabel    MACRO    p1,p2,p3
            ??tmp  =                  &p3&p2
                  @MakeJmpLabel2      p1,p2,%??tmp
                  ENDM
            ;;
            ;; ** @IfTrue ************ STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "IF" - IF TRUE
            @IfTrue          MACRO    p1
                  LOCAL         iftrue
                   j&p1          iftrue    ;;перейти к секции "IF"
                   IFNDEF        ?if_level ;;установить новый уровень
                                           ;;вложенности
            ?if_level            =      10
                   ELSE
            ?if_level            =      ?if_level + 1
                   ENDIF
                   @TestSym      ?if_nest,%?if_level  ;;установить новый
                                        ;;номер последовательности
                   If            (?symdef)
                   @IncSym       ?if_nest,%?if_level
                   ELSE
                   @ZeroSym      ?if_nest,%?if_level
                   ENDIF
            ;; Вставить переход  в секцию "ELSE" или "IF NOT"
                   @MakeJmp      ?if_,%?if_level,?if_nest
            iftrue:
                   ENDM
            ;;
            ;; **  @IfElse *********** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "ELSE"
            @IfElse          MACRO
                   IFNDEF         ?if_level
            ; ОШИБКА - "@IfElse" без открытого оператора "@IfTrue"
                   EXITM
                   ENDIF
                   IF(?if_level LT 10)
            ; ОШИБКА - "@IfElse" без открытого оператора "@IfTrue"
                   EXITM
                   ENDIF
            ;; Сгенерировать код "@IfElse"
                   @IncSym       ?if_nest,%?if_level

                                      - 1-35 -
                   @MakeJmp       ?if_,%?if_level,?if_nest
                   @DecSym        ?if_nest,%?if_level
                   @MakeJmpLabel  ?if_,%?if_level,?if_nest
                   @IncSym        ?if_nest,%?if_level
                   ENDM
            ;;
            ;; ** @IfEnd *********** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "END" для совместного
            ;; использования с "@IfTrue"
            @IfEnd  MACRO
                    IFNDEF    ?if_level
            ; ОШИБКА - "@IfEnd" без открытого оператора "@IfTrue"
                   EXITM
                   ENDIF
                   IF  (?if_level LT 10)
            ; ОШИБКА - "@IfEnd" без открытого оператора "@IfTrue"
                   EXITM
                   ENDIF
            ;; Сгенерировать метку "@IfEnd"
                   @MakeJmpLabel  ?if_,%?if_level,?if_nest
            ?if_level     =       ?if_level - 1
                   ENDM
            ;;
            ;; ** @DoWhile ******** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "DO_WHILE"
            @DoWhile       MACRO  p1,p2,p3
                   LOCAL   iftrue
                   IFNDEF  ?do_level      ;;установить новый уровень
                                          ;;вложенности
            ?do_level      =      10
                   ELSE
            ?do_level      =      ?do_level + 1
                   ENDIF
            ;; Установить номер новой последовательности для уровня
            ;; вложенности
                   @TestSym       ?do_nest,%?do_level
                   IF             (?symdef)
                   @IncSym        ?do_nest,%?do_level
                   ELSE
                   @ZeroSym       ?do_nest,%?do_level
                   ENDIF
            ;; Вставить метку начала цикла
                   @MаkeJmpLabel  ?do_,%?do_level,?do_nest
            ;; Вставить в программный код проверку условия
                   cmp      &p1,&p3
            ;; Перейти к секции "DO_WHILE_TRUE"
                   j&p2     iftrue
            ;; Перейти к следующей метке последовательности
                   @IncSym        ?do_nest,%?do_level
            ;; Вставить в программный код переход на конец цикла
                   @MakeJmp       ?do_,%?do_level,?do_nest
            ;; Начать секцию "DO_WHILE_TRUE"
            iftrue:
                   ENDM
            ;;
            ;; ** @DoExit *********** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "DO_EXIT" для совместного

                                      - 1-36 -
            ;; использования с "@DoWhile"
            @DoExit      MACRO
            ;; Вставить в программный код переход на конец цикла
                         @MakeJmp        ?do_,%?do_level,?do_nest
                         ENDM
            ;;
            ;; ** @DoEnd ************ STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "DO_END" для совместного
            ;; использования с "@DoWhile"
            ;; Макро @DoEnd генерирует программный код для структури-
            ;; рованного ENDDO
            @DoEnd       MACRO
                         IFNDEF          ?do_level
            ; ОШИБКА - "@DoEnd" без открытого оператора "@DoWhile"
                         EXITM
                         ENDIF
                         IF  (?do_level LT 10)
            ; ОШИБКА - "@DoEnd" без открытого оператора "@DoWhile"
                         EXITM
                         ENDIF
            ;; Переход на предыдущую метку последовательности
                         @DecSym         ?do_nest,%?do_level
            ;; Сгенерировать переход на начало цикла
                         @MakeJmp        ?do_,%?do_level,?do_nest
            ;; Перейти к следующей метке последовательности
                         @IncSym         ?do_nest,%?do_level
            ;; Сгенерировать метку для "@DoEnd"
                         @MakeJmpLabel   ?do_,%?do_level,?do_nest
            ?do_level      =       ?do_level - 1
                         ENDM
            ;;
            ;; ** @Repeat *********** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "@Repeat"
            ;; "@Repeat" генерирует программный код для структури-
            ;; рованного REPEAT-UNTIL
            @Repeat      MACRO
                   IFNDEF   ?rep_level   ;;установить новый уровень
                                          ;;вложенности
            ?rep_level     =        10
                   ELSE
            ?rep_level     =        ?rep_level + 1
                   ENDIF
            ;; Установить новый номер последовательности для уровня
            ;; вложенности
                   @TestSym         ?rep_nest,%?rep_level
                   IF               (?symdef)
                   @IncSym          ?rep_nest,%?rep_level
                   ELSE
                   @ZeroSym         ?rep_nest,%?rep_level
                   ENDIF
            ;; Вставить метку перехода на начало цикла
                   @MakeJmpLabel    ?rep_,%?rep_level,?rep_nest
                   ENDM
            ;;
            ;; ** @Until ***********STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "@Until" для совместного
            ;; использования с "@Repeat"

                                      - 1-37 -
            @Until       MACRO      p1,p2,p3
                         LOCAL      iftrue
                         IFNDEF     ?rep_level
            ; ОШИБКА - "@Until" без открытого оператора "@Repeat"
                         EXITM
                         ENDIF
                         IF (?rep_level LT 10)
            ; ОШИБКА - "@Until" без открытого оператора "@Repeat"
                         EXITM
                         ENDIF
            ;; Вставить в программный код проверку условия
                         cmp             &p1,&p3
            ;; Перейти к секции "@Until" .TRUE.
                         j&p2            iftrue
            ;; Вставить переход на начало цикла
                         @MakeJmp    ?rep_,%?rep_level,?rep_nest
            iftrue:
            ?rep_level     =        ?rep_level- 1
                         ENDM
            ;;
            ;; ** @For *********** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "@For". Используйте это
            ;; макро так:
            ;;     @For     counter,begin,end,dir,step
            ;;
            @For    MACRO           p1,p2,p3,p4,p5
                    LOCAL           first
                    LOCAL           iftrue
                    IFNDEF          ?for_level  ;;установить новый
                                    ;;уровень вложенности
            ?for_level     =        10
                    ELSE
            ?for_level     =        ?for_level + 1
                    ENDIF
            ;; Установить новый номер последовательности для
            ;; уровня вложенности
                    @TestSym        ?for_nest,%?for_level
                    IF              (?symdef)
                    @IncSym         ?for_nest,%?for_level
                    ELSE
                    @ZeroSym        ?for_nest,%?for_level
                    ENDIF
            ;; Вставить в программный код инициализацию счетчика -
            ;; (обойти 1-ый шаг)
                    mov             &p1,&p2 ;инициализировать счетчик
                    jmp             first   ;начать цикл FOR
            ;; Вставить метку перехода на начало цикла
                    @MakeJmpLabel   ?for_,%?for_level,?for_nest
            ;; Вставить в программный код шаг вычислений и выполнить
            ;; его проверку
                    IFIDN           <p4>,<+>
                    inc             &p1     ;увеличить счетчик
                    ELSE
                    IFIDN           <p4>,<->
                    dec             &p1     ;уменьшить счетчик
                    ELSE
            ; ОШИБКА - неверная спецификация шага в операторе "@For"

                                      - 1-38 -
                    EXITM
                    ENDIF
                    ENDIF
            first:           ;проверить на необходимость продолжения
            ;; Вставить в программный код проверку условия
                    cmp             &p1,&p3  ;достигнут ли конец?
            ;; Перейти к секции "FOR_TRUE"
                    IFIDN           <p4>,<+>
                    jl              iftrue   ;нет - продолжить цикл FOR
                    ELSE            ;;по умолчанию - к шагу "-"
                    jg              iftrue   ;нет - продолжить цикл FOR
                    ENDIF
            ;; Перейти к следующей метке последовательности
                    @IncSym         ?for_nest,%?for_level
            ;; Вставить в программный код переход на конец цикла
                    @MakeJmp        ?for,%?for_level,?for_nest
            iftrue:
                    ENDM
            ;;
            ;; ** @ForEnd ******** STRUCTURED CONTROL MACRO *****
            ;; Структурированное макро "FOR_END" для совместного
            ;; использования с "FOR"
            ;; @ForEnd генерирует программный код для структури-
            ;; рованного цикла FOR
            @ForEnd MACRO
                    IFNDEF          ?for_level
            ; ОШИБКА - "@ForEnd" без открытого оператора "FOR"
                    EXITM
                    ENDIF
                    IF  (?for_level LT 10)      page_end12
            ;
                    EXITM
                    ENDIF
            ;;
                    @DecSym          ?for_nest,%?for_level
            ;;
                    @MakeJmp         ?for_,%?for_level,?for_nest
            ;;
                    @IncSym          ?for_nest,%?for_level
            ;;
                    @MakeJmpLabel    ?for_,%?for_level,?for_nest
            ?for_level       =       ?for_level- 1
                    ENDM
            ;; **************************************************

                      Как работают структурированные макросы

            Сложность этих  макросов  вытекает  из необходимости поддержки
         вложенных структур управления.  Рассмотрим пример, приведенный на
         Рис.1-2.  Каждая  конструкция  IF-THEN-ELSE  требует наличия трех
         операторов перехода с тремя уникальными метками. Однако для запо-
         минания уникальных меток, сгенерированных директивой LOCAL, мы не
         можем использовать символы - нам приходится создавать собственные
         метки на базе счетчиков. Это обеспечивает прямое управление зада-
         чей.
            Для единичных  уровней вложенности достаточно простого счетчи-
         ка. Обратите внимание,  как на Рис. 1-2 конструкция IF-THEN-ELSE,
         связанная  с  условием  b,  использует  последовательность  меток
         3,4,5.  Это реализовать просто,  так как метки используются в том

                                      - 1-39 -
         же порядке, что и команды перехода, и метки перехода. Однако, как
         только появляются вложенные структуры управления, простой счетчик
         не справляется. Мимолетный взгляд на последовательность меток для
         всех трех операторов IF-THEN-ELSE выявляет  серьезный  недостаток
         последовательности.  Данная  проблема решается использованием для
         каждого уровня вложенности своего счетчика.
            Уникальные метки  создаются  посредством включения в каждую из
         них трех элементов информации.  Первым элементом является иденти-
         фикатор типа структуры, например, ?if_us, do_, ?rep_.Знак вопроса
         используется для уменьшения вероятности конфликта  с создаваемыми
         пользователем  символами  или метками.  Второй элемент информации
         представляет собой уровень вложенности,  который используется для
         различения  между  номером  метки n на одном уровне вложенности и
         номером метки n на основном уровне вложенности и номером  метки n
         на другом уровне вложенности.Наконец, для обеспечения уникальнос-
         ти метки каждого перехода конкретного уровня вложенности  включа-
         ется значение счетчика.
            СТРУКТУРА УПРАВЛЕНИЯ             НА АССЕМБЛЕРЕ

                              [        j(a)     1_1:
                              [        jmp      1_2:
            IF(условие а) ----[  L_1:        .       (а)старт true-
                              [              .                    |
                                             .                    |
                               [       j(b)     1_3:              |
               IF(условие b)---[       jmp      1_4:              |
                               [  L_3:                (b) кoд true|
                                             .                    |
                                             .                    |
                                             .                    |
                              [         jmp     1_5:              |
               ELSE ----------[   L_4:       .       (b) код false|
                              [              .                    |
                                             .                    |
               ENDIF -----------  L_5:              (a) конец true-
                             [          jmp     1_6:
            ELSE ------------[    L_2:             (a) старт false-
                             [               .                    |
                                             .                    |
                                             .                    |
                               [        j(c)    1_7:              |
               IF(условие с) --[        jmp     1_8:              |
                               [  L_7:             (c) код true   |
                                             .                    |
                                             .                    |
                                             .                    |
                              [         jmp     1_9:              |
               ELSE-----------[   L_8:              (c) код false |
                              [              .                    |
                                             .                    |
                                             .                    |
               ENDIF------------- L_9:             (a) конец false-

            ENDIF---------------- L_6:

            Рис.1-2. Структура управления IF и  соответствующая  ей
                     интерпретация на языке ассемблера

                                      - 1-40 -
            Для сравнения  эти уникальные составные метки, сгенерированные
         нашими структурированными макросами,  показаны в  Листинге  1-15.
         Первые две цифры числа являются уровнем вложенности, значение ко-
         торого начинается с 10 с тем, чтобы для уровня вложенности всегда
         были  зарезервированы  две  цифры.  Это  предотвращает совпадение
         уровня 1 счетчика 11 (1-11) с уровнем 11 счетчика 1 (11-1).
            Краткий текст программы в  точности  соответствует  тому,  что
         представлено на Рис.  1-2.  При детальном рассмотрении мы увидим,
         что расширенные макро на языке ассемблера создают те же  структу-
         ры, что представлены на Рис.1-2.
            Так как метки состоят из трех частей,  каждый тип макро струк-
         турного управления должен поддерживать набор счетчиков.  Этот на-
         бор включает в себя символ счетчика для указания  текущего уровня
         вложенности. Для обобщения задачи сопровождения этих счетчиков мы
         создали следующие макросы:  testsym,  zerosym,  incsym и  decsym.
         Этим  макросам  передаются аргументы,  которые они используют для
         создания счетчиков.  Аргументы представляют собой  идентификаторы
         типа (?if_) и текущие уровни вложенности.


                   Приемы кодирования и некоторые предупреждения

            Когда необходимо  создать  действительную  команду  перехода и
         метку перехода,  мы будем  использовать  макросы  mkjmp,  mkjmp2,
         mklbl  и  mklbl1.  Действительные метки состоят из идентификатора
         типа и номеров.  Единственный способ получить  числовое  значение
         символа заключается  в применении оператора процента (%), который
         действителен только при использовании с аргументом  вызова макро.
         Мы хотим вычислить символ, определяемый двумя элементами информа-
         ции из счетчика, так:

            mkjmp2      p1,p2,%&p3&p2

            Однако Руководство по MASM сообщает нам,  что оператор  ампер-
         санда  (&) не может быть использован в вызове макро.  Таким обра-
         зом, мы должны создать временную переменную и использовать ее.

            ??tmp =     &p3&p2
                    mkjmp2 p1,p2,%??tmp

            Все это  влечет за собой следующее.  Первая форма,  содержащая
         амперсанды в вызове макро,  должна работать. Однако выбор скрытой
         возможности вызывает проблемы будущей совместимости и даже реали-
         зуемости. Кроме того, Вы всегда должны задаваться вопросом, может
         ли неподдерживаемая   или несанкционированная функция зависеть от
         реализации совместимости.  Решение этой дилеммы остается за чита-
         телем.
            Авторы использовали  эту  несанкционированную  возможность   в
         программе,  не генерирующей программного кода, но решающей знаме-
         нитую задачу "Ханойские башни" в рекурсивной манере.  Кроме того,
         для  достижения общности наш метод создания символов счетчиков из
         нескольких частей позволяет  при  необходимости  создавать  новые
         счетчики.  Перед своим использованием эти счетчики должны инициа-
         лизироваться,  в противном случае первая  попытка  увеличить  или

                                      - 1-41 -
         уменьшить  их значение вызовет ошибку "Символ не определен".  Ис-
         пользуя условный оператор IFDEF,  можно проверить,  требуется  ли
         инициализация при каждом использовании символа.
            Инициализация связана  еще с одной тонкостью работы MASM.  Как
         мы установили, MASM является двухпроходным ассемблером, определя-
         ющим  символы при первом проходе и затем использующим их при вто-
         ром.  Это значит,  что определения символов защищены  от  первого
         прохода  ко  второму.  Таким образом,  когда MASM начинает второй
         проход,  все счетчики первого прохода уже определены  и  содержат
         свои старые значения. Если в начале второго прохода переинициали-
         зация символов не происходит,возникает фазовая  ошибка,  так  как
         начальные значения счетчиков отличаются.
            Для инициализации символов на первом проходе необходима  конс-
         трукция  IFDEF,  так  как заранее мы не знаем,  сколько счетчиков
         потребуется,  а использования IFDEF на втором проходе недостаточ-
         но.  Мы решили эту проблему, создав символы ?р2sw ..., которые на
         втором проходе анализируются на необходимость установки в нулевые
         значения.  Имя получается из Switch (переключателя) фазы 2.  Этот
         процесс проверки предоставляет хорошую возможность выявить,  при-
         надлежат  ли уровни вложенности самому верхнему уровню, указывая,
         что конструкции IF-IFEND, DOWHILE-DOEND и т.д. спарены правильно.
            В Листинге  1-16 приведены простые примеры расширения макросов
         структурного управления, определенных ранее. Как можно видеть, мы
         подавили  те  части  расширения,  которые не вырабатывают код или
         метки переходов.  Если Вы хотите ознакомиться с работой этих мак-
         росов  более  детально,  используйте директиву .LALL.  Прибегайте
         только к сокращенному примеру, так как обработка этих макро вызы-
         вает выполнение множества шагов.  Количество шагов также объясня-
         ет, почему происходит увеличение требуемого времени ассемблирова-
         ния   программы.   Используя  эти  макро,  не  ожидайте  быстрого
         ассемблирования, но рассчитывайте на быстрое кодирование.


                  Листинг 1-15. Вложенная структура IF-THEN-ELSE

         ----------------------------------------------------------------
                         ; Сжатый исходный код программы

                      @IfTrue e             условие (a)
                        @IfTrue e           условие  (b)
                        @IfElse             "else" для условия (b)
                        @IfEnd              конец условия (b)
                      @IfElse               "else" для условия (a)
                        @IfTrue e           условие (c)
                        @IfElse             "else" для условия (c)
                        @IfEnd              конец условия (c)
                      @IfEnd                конец условия (a)
                         ; Листинг расширения
                      @IfTrue e             условие (a)
            1         je    ??0000
            3         jmp     ?if_100
            1 ??0000:
              ; выполнить, если условие (а) истинно
                      @IfTrue e             условие (b)
            1         je    ??0001
            3         jmp     ?if_110

                                      - 1-42 -
            1 ??0001:
              ; выполнить, если условие (b) истинно
                      @IfElse               "else" для условия (b)
            3         jmp     ?if_111
            3 ?if_110:
              ; выполнить, если условие (b) не истинно
                      @IfEnd                конец условия (b)
            3 ?if_111:
                      @IfElse               "else" для условия (a)
            3         jmp     ?if_101
            3 ?if_100:
              ; выполнить, если условие (а) не истинно
                      @IfTrue e              условие (c)
            1         je    ??0002
            3         jmp     ?if_112
            1 ??0002:
              ; выполнить, если условие (с) истинно
                      @IfElse               "else" для условия (c)
            3         jmp     ?if_113
            3 ?if_112:
              ; выполнить, если условие (с) не истинно
                      @IfEnd                конец условия (c)
            3 ?if_113:
                      @IfEnd                конец условия (a)
            3 ?if_101:
         ---------------------------------------------------------------


               Листинг 1-16. Расширение  структурированных макросов

         ----------------------------------------------------------------
                      @IfTrue e
            1         je    ??0000
            3         jmp     ?if_100
            1 ??0000:
              ; Выполнить, если истина
                      @IfElse
            3         jmp     ?if_101
            3 ?if_100:
              ; Выполнить, если не истина
                      @IfEnd
            3 ?if_101:
              ;- - - - - - - - - - - - - - - - - - - - - - - - - -
                      @DoWhile ax,le,bx
            3 ?do_100:
            1         cmp      ax,bx
            1         jle    ??0001
            3         jmp     ?do_101
            1 ??0001:
              ; Выполнять пока  ax <= bx
                      @DoExit
            3         jmp     ?do_101
              ; Выйти из программы
                      @DoEnd
            3         jmp      ?do_100
            3  ?do_101:
            ;- - - - - - - - - - - - - - - - - - - - - - - - - -  - -

                                      - 1-43 -
                       @Repeat
            3  ?rep_100:
               ; Выполнять пока соблюдается условие
                       @Until   ax,e,bx
            1          cmp      ax,bx
            1          je     ??0002
            3          jmp      ?rep_100
            1  ??0002:
               ;- - - - - - - - - - - - -- - - - - - - - - - - - - - -
                       @For     ax,10,20,+
            1          mov      ax,10       ;инициализировать счетчик
            1          jmp      ??0003      ;начать цикл FOR
            3  ?for_100:
            1          inc      ax          ;увеличить счетчик
            1  ??0003:        ;проверить на необходимость продолжения
            1          cmp      ax,20       ;достигнут ли конец
            1          jl       ??0004      ;нет - продолжить цикл
            3          jmp      ?for_101
            1  ??0004:
               ; Выполнить для ах = 10 до 20 с шагом 2
                       @ForEnd
            3          jmp      ?for_100
            3  ?for_101:
         ----------------------------------------------------------------

                                 Макро псевдо-CASE

            Последнее макро,  которое мы ввели  в  этой  главе,есть  макро
         псевдо-case,  представленное Листингом 1-17. Так как макро должно
         иметь "предвидение" о поддерживаемых в нем структурах,  мы не бу-
         дем  рассматривать  его как оператор структурного управления.  По
         своим функциям макро case более походит на  блок диспетчеризации,
         типа вычисляемого оператора GOTO в языке Фортран.


                     Листинг 1-17. Описание макро псевдо-саse

         ----------------------------------------------------------------
            @Case        MACRO    кey,сase_list,jmр_labels
                         ??tmp_1  = 0
                         IRP      match,<&case_list> ;;последователь-
                                                    ;;ность вариантов
                           ??tmp_1= ??tmp_1 + 1  ;;установить номер
                                                 ;;индекса
                           cmp    key,&&match  ;вариант найден?
                           ??tmp_2= 0
                           IRP    retl,<&jmp_labels> ;;последователь-
                                                    ;;ность переходов
                           ??tmp_2=0 = ??tmp_2 + 1  ;; до достижения
                                                    ;; индекса
                             IF   (??tmp_1 EQ ??tmp_2)
                               je &&&retl                     ; Да!
                               EXITM
                             ENDIF    ;;конец проверки условия
                           ENDM       ;;закончить 2-ой блок IRP
                         ENDM         ;;закончить 1-ый блок IRP
                         ENDM         ;;закончить макроописание
         ---------------------------------------------------------------

                                      - 1-44 -

         Это макро являет собой хороший пример одновременного синтаксичес-
         кого анализа двух списков.  Внешний цикл, irp match,<&case_list>,
         устанавливает последовательность элементов  списка  вариантов,  а
         внутренний цикл, irp retl,<&jmp_labels>, выбирает соответствующие
         метки переходов.Такое решение может быть использовано для  реали-
         зации макросов подстановки.
            В макросах подстановки внешний цикл  устанавливает  последова-
         тельность элементов списка и выявляет совпадение. После определе-
         ния совпадения,  скажем на элементе xth, макро входит во внутрен-
         ний  цикл  и  устанавливает последовательность элемента xth этого
         списка. Одним из возможных вариантов реализации может быть созда-
         ние макро переход_по_невыполнению_условия,  где выбранный переход
         должен был бы заменяться на противоположный.  Еще  раз  напомним,
         что во вложенных макроблоках необходимо использовать дополнитель-
         ные амперсанды.
            Расширение макро  @Case  представлено в Листинге 1-18.  За то,
         чтобы в каждом списке появлялся один и тот же номер элемента, от-
         вечает программист. В противном случае может быть получена невер-
         ная структура управления.


                    Листинг 1-18. Расширение макро псевдо-@Case

         ----------------------------------------------------------------
                @Case    al,<'A','B','C','D'>,<subA,subB,subC,subD>
            2              cmp   al,'A'    ;вариант соответствует?
            3              je    subA               ; да!
            2              cmp   al,'B'    ;вариант соответствует?
            3              je    subB               ; да!
            2              cmp   al,'C'    ;вариант соответствует?
            3              je    subC               ; да!
            2              cmp   al,'D'    ;вариант соответствует?
            3              je    subD               ; да!
                subA:
                         jmp     merge
                subB:
                         jmp     merge
                subC:
                         jmp     merge
                subD:
                         jmp     merge
                default:
                merge:
         ----------------------------------------------------------------



                                  Макросы данных

            Макросы могут  использоваться  для  генерации данных или прог-
         раммного кода. В обоих случаях подход и методы одинаковы, однако,
         в целях изучения рассмотрим макросы,  генерирующие только данные.
            Простейшим примером команды MASM, генерирующей данные, являет-
         ся:

                                      - 1-45 -

            TenBytes      DB      10  DUP 4     ;зарезервировать 10
                                                ;байтов под номером 4

            Эта команда имеет ограниченное применение,  так как она  пред-
         почтительна, когда мы хотим получить последовательность чисел как
         в индексном наборе.  В качестве примера зарезервируем N слов дан-
         ных в наборе чисел от 1 до N:

         @FirstTry    MACRO  N       ;;определить макро с параметром N
                    NUMB = 0         ;;инициализировать число
                      REPT N         ;;повторить нижеследующее N раз
                      NUMB = NUMB+1  ;; увеличить индекс
                      DW    NUMB     ;;определить слово NUMB
                      ENDM           ;;закончить команду REPT
                    ENDM             ;;конец макро

            Заметим, что  для каждой директивы MACRO должно присутствовать
         ENDM.  Первой переменной, NUMB, значение присваивается оператором
         =, а не EQU, что позволяет изменять ее значение в блоке REPT.
            Директива REPT представляет собой циклическую  структуру, типа
         do...while языка высокого уровня.  Она повторяет действия, заклю-
         ченные между REPT и ENDM,  N раз. В данном случае происходит уве-
         личение NUMB на 1, а затем создается слово, содержащее это число.
         (Имейте в виду,  что Вы указываете MASM на создание констант, ко-
         торые  будут ассемблироваться.  Вы не указываете компьютеру цикл,
         подлежащий проходу во время выполнения программы)
            Если макроописание  FirstTry поместить в начало нашей програм-
         мы,  а затем использовать его в сегменте данных с N, равным 4, мы
         получим:
            @FirstTry 4
         что соответствует тому, что MASM будет ассемблировать четыре сло-
         ва чисел от 1 до 4.
            Это слишком простой пример использования макро.  Разрешите ус-
         ложнить его, создав таблицу двоично-десятичных чисел, которая мо-
         жет служить таблицей просмотра при преобразовании шестнадцатирич-
         ных данных в код BCD.


            @BCDtable  MACRO  N    ;;определить макро с параметром N
                    NUMB = 0       ;;инициализировать числа
                    HIGHBYTE = 0
                      REPT N       ;;повторить нижеследующее N раз
                      NUMB = NUMB+1  ;;увеличить индекс
                      IF (NUMB GT 9)
                      NUMB = 0
                      HIGHBYTE = HIGHBYTE + 10H
                      ENDIF
                      IF (HIGHBYTE GT 90H)
                      EXITM
                      ENDIF
                    BCDNUMB = (NUMB OR HIGHBYTE)
                    DW BCDNUMB   ;;определить слово с именем NUMB
                  ENDM          ;; конец команды REPT
                ENDM            ;; конец макро


                                      - 1-46 -

            Этот пример значительно сложнее,  но он не представляет ничего
         особенного для опытного программиста.  Прежде чем провести  пост-
         рочный анализ этих директив (термин "директива" мы используем для
         обозначения того,  что является командой для MASM,  а не для ЦП),
         разрешите рассмотреть результат работы программы с N, установлен-
         ным в 20:

            38                  @BCDtable   20
            39 0004  0001        2 DW   BCDNUMB  ;
            40 0006  0002        2 DW   BCDNUMB  ;
            41 0008  0003        2 DW   BCDNUMB  ;
            42 000A  0004        2 DW   BCDNUMB  ;
            43 000C  0005        2 DW   BCDNUMB  ;
            44 000E  0006        2 DW   BCDNUMB  ;
            45 0010  0007        2 DW   BCDNUMB  ;
            46 0012  0008        2 DW   BCDNUMB  ;
            47 0014  0009        2 DW   BCDNUMB  ;
            48 0016  0010        2 DW   BCDNUMB  ;
            49 0018  0011        2 DW   BCDNUMB  ;
            50 001A  0012        2 DW   BCDNUMB  ;
            51 001C  0013        2 DW   BCDNUMB  ;
            52 001E  0014        2 DW   BCDNUMB  ;
            53 002O  0015        2 DW   BCDNUMB  ;
            54 0022  0016        2 DW   BCDNUMB  ;
            55 0024  0017        2 DW   BCDNUMB  ;
            56 0026  0018        2 DW   BCDNUMB  ;
            57 0028  0019        2 DW   BCDNUMB  ;
            58 002A  0020        2 DW   BCDNUMB  ;

            Первый столбец представляет собой номера  строк  ассемблерного
         листинга,  второй - смещение адреса относительно модуля, а третий
         - то, что мы хотели - таблицу чисел BCD от 1 до 20.
            Теперь построчно рассмотрим все макро. Прежде всего мы инициа-
         лизируем две переменные.  NUMB будет зациклена от 1 до 9,  предс-
         тавляя  младший байт,  в то время как HIGHBYTE будет представлять
         байт более высокого порядка. Остальной частью макро управляет ди-
         ректива REPT. Первым делом внутри блока повторения мы увеличиваем
         на 1 переменную NUMB.  Затем определяем, равна ли она 10, и, если
         это так, для очередного запуска цикла устанавливаем ее в 0. Затем
         добавляем к HIGHBYTE - 10, увеличивая цифру десятков числа в фор-
         мате BCD. Далее завершаем оператор IF.
            Следующим шагом проверяем,  если построенное нами число в  BCD
         больше  того,  что может храниться в слове,  то выходим из макро.
         Предпоследним действием создаем число в  BCD,  выполняя  операцию
         логического  "ИЛИ" над цифрой единиц и цифрой десятков.  Наконец,
         создаем слово,  содержащее требуемое число в BCD. Первый ENDM за-
         вершает цикл REPT;  второй - завершает макро. Для ссылки к списку
         чисел BCD необходима метка.  Мы не хотим  набирать  метку  каждый
         раз, когда обращаемся к макро. Мы используем оператор подстановки
         @, чтобы эту метку создавал MASM:

            @BCDtable MACRO N    ;;определить макро с параметром N
                    BCD1to&N label word       ;;определить метку
                    NUMB = 0     ;;инициализировать числа
                    HIGHBYTE = 0
                      REPT N     ;;повторить нижеследующее N раз

                                      - 1-47 -
                      NUMB = NUMB+1   ;;увеличить индекс
                        IF (NUMB GT 9)
                        NUMB = 0
                        HIGHBYTE = HIGHBYTE + 10H
                        ENDIF
                        IF (HIGHBYTE GT 90H)
                        EXITM
                        ENDIF
                      BCDNUMB = (NUMB OR HIGHBYTE)
                      DW    BCDNUMB ;;определить слово с именем NUMB
                      ENDM          ;;конец команды REPT
                    ENDM            ;;конец макро

            Теперь в листинге наше макро выглядит так:

            31                   @BCDtabel   20
            32 0004          1   BCD1to20  label word ;определить метку
            33 0004  0001    2       DW    BCDNUMB         ;
            34 0006  0002    2       DW    BCDNUMB         ;
            35 0008  0003    2       DW    BCDNUMB         ;
            и т.д.

            Знак амперсанда  (&)  сообщает MASM о необходимости подставить
         значение N,  используемое при инициировании макро.  Но и это  нас
         еще  не  удовлетворяет.  Наличие  у списка чисел BCD только одной
         метки заставляет нас для доступа к списку  использовать  индекс.
         Мы бы хотели иметь метку у каждого элемента списка.  Оператор вы-
         ражения %  позволит нам иметь значение каждого числа и  использо-
         вать его как часть метки.  Мы переписываем наше макро в виде двух
         следующих макро:

            @BCD     MACRO      NAME,NUMBER    ;;NAME для метки
                            ;;NUMBER для данных
                     BCDof&NAME     DW  NUMBER ;;определить слово с
                                    ;;NUMBER в коде BCD
                     ENDM           ;;конец макро
            ;;
            @BCDtable MACRO N  ;;определить макро с параметром N
                     NUMB  = 0 ;;инициализировать числа
                     INDEX = 0
                     HIGHBYTE        = 0
                       REPT N   ;;повторить нижеследующее N раз
                       INDEX = INDEX + 1
                       NUMB  = NUMB + 1   ;;увеличить индекс
                         IF (NUMB GT 9)
                       NUMB        = 0
                       HIGHBYTE = HIGHBYTE + 10H
                       ENDIF
                       IF (HIGHBYTE  GT 90H)
                       EXITM
                       ENDIF
                       BCDNUMB = (NUMB OR HIGHBYTE)
                       @BCD %INDEX,BCDNUMB    ;;INDEX для метки
                                 ;;BCDNUMBER для данных
                       ENDM      ;;конец команды REPT
                     ENDM        ;;конец макро


                                      - 1-48 -

            Теперь, когда  мы смотрим листинг,  мы видим,  что каждый байт
         таблицы чисел BCD имеет соответствующую метку:

                        @BCDtable  20
            0004  0001   3 BCDof1  DW  BCDNUMB  ;определить число с
            0006  0002   3 BCDof2  DW  BCDNUMB  ;      NUMBER
            0008  0003   3 BCDof3  DW  BCDNUMB  ;        в
            000A  0004   3 BCDof4  DW  BCDNUMB  ;   коде BCD
            .
            .
            .

            Таким образом можно создавать сложные таблицы.Если есть форму-
         ла типа (N x M)/((P+Q) MOD T),  вместо ручного заполнения таблицы
         мы можем поручить заботы по ее созданию MASM.
            Проверять ситуацию переполнения мы могли бы,  включив в  текст
         нашего макро что-нибудь вроде следующего:

            IFE (BCDNUMB LE 0FFFFh) ;;больше, чем может хранить слово?
            DW      BCDNUMB         ;;достаточно мало
            ELSE
            %OUT ERROR IN @BCD MACRO

            Оператор OUT выводит сообщение на экран во время ассемблирова-
         ния  -  в данном случае сообщение "ERROR IN @BCD MACRO" (ошибка в
         макро @BCD).
            До сих пор мы использовали параметры, как конкретные элементы,
         разделенные запятыми. В качестве одиночного параметра макро также
         возможно иметь набор элементов,  который будет использоваться для
         итеративного создания данных.  Например, если мы хотим установить
         список сообщений,  подлежащих выводу на экран, мы можем закодиро-
         вать макронабор:

            @OptDisp MACRO OptType,Options ;; OptType = метка,
                                           ;; Options = список
            OptType&List     db   Options
            ENDM                           ;;конец макро

         Затем мы можем использовать егo в сегменте данных:

            @OptDisp LineSpeed,<'1200sq],'2400','4800'>

            Linespeed - будет заменено в метке,  и каждая строка в угловых
         скобках будет вставлена в директиву db, как если мы набрали:

            LineSpeedList   db      '1200'
                            db      '2400'
                            db      '4800'

            Подобное применение ограничено,  так как при доступе к  строке
         мы исходим из знания, что она имеет в длину 4 символа. Значитель-
         но чаще мы имеем дело со строками переменной длины, заканчивающи-
         мися  нулем  в  коде ASСII.  Ниже приводится макро создания таких
         строк:


                                      - 1-49 -
            @MakeList MACRO Name2,messag
                    MESSAGE&Name2    db   CR,LF,messag,CR,LF,0
                    ENDM
            ;;
            @OptDisp MACRO Options ;;OptType = метка,Options =  список
                    Name3 = 0
                      IRP msg,<Options>
                      Name3 = Name3 + 1
                      @MakeList %Name3,msg
                      ENDM
                    ENDM             ;;конец макро
            Строки сегмента данных мы можем использовать так:

            @OptDisp <'Error','Waiting','Computing'>

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

                              @OptDisp <'Error','Waiting','Computing'>
           0D 0A 45 72 72 6F 72 3 MESSAGE1 db CR,LF,'ERROR',CR,LF,0
           0D 0A 57 61 69 74 69 3 MESSAGE2 db CR,LF,'Waiting',CR,LF,0
           0D 0A 43 6F 6D 70 75 3 MESSAGE3 db CR,LF,'Computing',CR,LF,0
            Поучительным моментом данного макро является то,  что для соз-
         дания необходимого числа строк мы использовали  в  директиве  IRP
         оператор  литерального  текста  (<  >).Однако у нас еще не решена
         проблема доступа к этому списку строк. Нам необходим список адре-
         сов. Следующее макро представляет решение этой проблемы.
            @MakeList МACRO Name2,messag
                    MESSAGE&Name2   db    CR,LF,messag,CR,LF,0
                    ENDM
            ;;
            @MakeNames  MACRO Name5
                    db       MESSAGE&Name5
                    ENDM                   ;;конец REPT
            ;;
            @OptDisp MACRO Options ;;OptType =  метка, Options = список
                    Name3 = 0
                      IRP   msg,<Options>
                      Name3 = Name3 + 1
                      @MakeList  %Name3,msg
                      ENDM
                      Name4 = 0
                      MessageList Label WORD
                        REPT Name3
                        Name4 = Name4 + 1
                        @MakeNames  %Name4
                        ENDM                  ;;конец REPT
                      ENDM                    ;;конец макро
            Когда макро  используется  в секции данных,  мы получим тот же
         результат, как если бы набрали следующее:
            @OptDisp  <'Error','Waiting','Computing'>
            MESSAGE1    db   CR,LF,'Error',CR,LF,0
            MESSAGE2    db   CR,LF','Waiting',CR,LF,0
            MESSAGE3    db   CR,LF,'Computing',CR,LF,0
            MessageList Label   WORD
            dw      MESSAGE1
            dw      MESSAGE2
            dw      MESSAGE3

                                      - 1-50 -
            Конечно, для генерации  данных  при помощи макро можно сделать
         гораздо больше.  Мы лишь подбросили идею некоторых  возможностей.
         Те  же  методы могут быть использованы для генерации программного
         кода, что мы теперь и рассмотрим.

                        Макросы генерации программного кода
            Макросы представляют собой мощный механизм передачи ассемблеру
         некоторых действий по программированию.  Также, как Вы можете пи-
         сать программу на Бейсике,  заставляющую  компьютер выполнять за-
         данную Вами работу,  Вы можете написать программу на МAСRO,  зас-
         тавляющую  ассемблер,  в  данном  случае MASM,  выполнять для Вас
         неинтересную часть работы по программированию.  Ниже,  в качестве
         упрощенного примера того,  что мы имели в виду,  приведено макро,
         реализующее запись символа в файл:
            @WritToFil MACRO    ;;определить макро
                   mov       ah,40h   ;;функция DOS по записи в файл
                   int       21h                ;;вызов DOS
                   ENDM                         ;;конец макро
            Теперь вместо того,  чтобы каждый раз, когда мы хотим записать
         символ в файл, переписывать команды MOV и INT, мы можем использо-
         вать макро WritToFil.
         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         ¦      Сравнительные характеристики макросов и подпрограмм      ¦
         ¦                                                               ¦
         ¦   При помощи подпрограмм мы можем сделать то же самое,  что  и¦
         ¦при помощи макро,  однако оформление небольших программных кус-¦
         ¦ков в подпрограммы не эффективно.  Разница между макро и  под- ¦
         ¦программой  заключается в том, что макро вставляет нужный прог-¦
         ¦раммный код непосредственно в файл исходного текста, в то время¦
         ¦как  подпрограмма размещается где-нибудь в другом месте,  и для¦
         ¦выполнения ее программного кода мы должны  осуществлять переход¦
         ¦по месту ее расположения. Другими словами, использование макро-¦
         ¦сов для создания повторяющегося линейного кода ликвидирует нак-¦
         ¦ладные расходы при выполнении программы,  обусловленные вызовом¦
         ¦и возвратом из подпрограмм.                                    ¦
         ¦   Мы используем макросы вместо подпрограмм из тех же соображе-¦
         ¦ний,  что для короткого разговора обращаемся к кому-либо по те-¦
         ¦лефону  вместо поездки к нему через весь город - потери времени¦
         ¦при переходе по другому адресу не оправдываются краткостью  на-¦
         ¦шей задачи.  Кроме того,  макрокод имеет тенденцию к уменьшению¦
         ¦своих размеров,  так как он добавляется к программе всякий раз,¦
         ¦когда  должен использоваться.  Если он получается слишком длин-¦
         ¦ным,  его следует оформить в подпрограмму.  Что значит "Слишком¦
         ¦длинный"? Это зависит от накладных расходов, необходимых на вы-¦
         ¦зов подпрограммы, от того, как часто используется функция, и от¦
         ¦отношения значения памяти к скорости выполнения программы.     ¦
         ¦   Макросы работают быстрее,  так как не требуют сохранения ре-¦
         ¦гистров,помещения в стек параметров и т.д. Однако частые повто-¦
         ¦рения коротких макросов  могут  занимать  значительную память в¦
         ¦объектных и исполнимых  файлах. Сначала напишите макрос и, если¦
         ¦окажется,  что он становится неуправляем, перепишите его в под-¦
         ¦программу. Позже мы увидим,как можно оформить вызов подпрограмм¦
         ¦в виде макро.                                                  ¦
         АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

                                      - 1-51 -

                                 Условные макросы

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

            @WritToFil MACRO EКOFLAG   ;;определить INCHRIF с
                                   ;;аргументом EKOFLAG
            IFIDN <EKOFLAG>,<EKO>  ;;если аргумент EKOFLAG
                                   ;;идентичен трем буквам
                                   ;;EKO, ассемблировать
                                   ;;следующую строку
            mov      ah,06h  ;;функция DOS по записи в
                             ;;стандартный вывод
            ELSE       ;;если EKOFLAG не идентично трем буквам
                       ;; EKO, ассемблировать следующую строку
            mov      ah,40h    ;;функция DOS по записи в файл
            int      21h                     ;;вызов DOS
            ENDM                             ;;конец макро

            В данном случае MASM анализирует аргумент EKOFLAG с целью  оп-
         ределения, что вставлять: mov ah,06h или mov ah,40h:

            @WritToFil EKO    ;здесь MASM подставляет MOV AH,06  и
                              ;INT 21H
                    .
                    .
                    .         ;так как аргумент идентичен EKO
            @WritToFil NOEKO  ;MASM подставляет MOV AH,40H и
                              ;INT 21H
                    .
                    .
                    .         ;так как аргумент не идентичен EKO


            Заметим, что в предыдущем примере вместо NOEKO мы могли бы ис-
         пользовать PHUBAH или что-нибудь еще, так как основная мысль зак-
         лючается в том,  чтобы аргументом не было EKО. Спеллинг параметра
         довольно произвольный.  Это гарантирует возможность ошибки,  если
         мы забудем его и напишем @WritToFil ECHO. Такая запись лишает нас
         появления эха на экране,  так как вместо EKO мы указали ECHO.  Мы
         можем исключить возможность появления такой ошибки, ограничив се-
         бя использованием EKO или NOEKO:

         @WritToFil MACRO EKOFLAG  ;;определить INCHRIF c аргументом
                                   ;;EKOFLAG
                    IFIDN <EKOFLAG>,<EKO> ;;если EKOFLAG = EKO,

                                      - 1-52 -
                                   ;;ассемблировать след-ую строку
                    mov     ah,06h    ;;функция DOS по записи в
                                   ;;стандартный файл
                    ELSE           ;;в противном случае
                    IFIDN <EKOFLAG>,<NOEKO> ;;если EKOFLAG = NOEKO,
                                            ;;ассемблировать
                    mov     ah,40h ;;функция DOS по записи в файл
                    ELSE           ;;если аргумент не соответствует,
                                            ;; то
                    .ERR   ;;выдать ошибку ассемблирования
                    ENDIF    ;;конец проверки условия
                    int     21h             ;; вызов DOS
                    ENDM                    ;; конец макро

                                 Вложенные макросы

            Рассмотренный нами  макрос  использует  функцию  DOS по записи
         символов на стандартное устройство вывода или в файл.  Однако  мы
         можем  захотеть проверить,  была ли нажата клавиша для прерывания
         вывода,  и если это не так, продолжить обработку. Функция DOS 0Bh
         проверяет, была ли нажата клавиша, возвращая AL = 0FFh, если сим-
         вол доступен, и AL = 00, если символ не доступен. Мы не можем на-
         писать  макро  chkchr  и  затем  вызывать  его  из нашего макроса
         WritToFil:

            @ChkChr MACRO    ;;определить макро @ChkChr
                    mov      ah,0Bh   ;;проверить стандартный ввод
                    int      21h      ;;вызов DOS
                    ENDM              ;;конец макро
            ;;
            @WritToFil MACRO WAITFLAG,EKOFLAG ;; 2 аргумента
                    LOCAL    bye   ;;определить формальный адрес
                    IFNB     <WAITFLAG>  ;;если поле для WAITFLAG не
                                         ;;пусто, ассемблировать
                                         ;;следующее
                    @ChkChr   ;;выявить, ожидается ли символ
                    cmp      al,0   ;; al = 0 => символ не ожидается
                    je       bye    ;;если символа нет, продолжить
                    ENDIF           ;;конец проверки условия
                    IFIDN    <EKOFLAG>,<EKO> ;;если EKOFLAG=EKO, ас-
                                             ;;семблировать
                    MOV      AH,06H          ;;функция DOS по записи
                                             ;;в стандартный вывод
                    ELSE            ;;в противном случае
                      IFIDN <ЕKOFLAG>,<NOEKO> ;;если EKOFLAG=NOEKO,
                                             ;;ассемблировать
                      MOV   AH,40H   ;;функция DOS по записи в файл
                      ELSE         ;;если аргумент не соответствует
                      .ERR         ;;выдать ошибку ассемблирования
                    ENDIF          ;;конец проверки условия
                    int     21h              ;;вызов DOS
            bye:
                    ENDM                     ;;конец макро


            Обсудим некоторые возможности новой версии WritToFil. Директи-
         ва LOCAL сообщает MASM,  что метка bye  является  формальной мет-

                                      - 1-53 -
         кой,которую MASM заменяет на реальную всякий раз, когда макро вы-
         зывается из программы. Это устраняет проблему использования одной
         и той же метки в программе дважды,  что вызвало бы ошибку ассемб-
         лирования.  MASM ассемблирует макро, используя в первый раз метку
         ??0000,  во второй раз - ??0001 и т.д.  до ??FFFFh, что позволяет
         вызвать  макро  в  одной программе до 65536 раз.  Директива LOCAL
         должна следовать сразу же за директивой MACRO - перед ней не  мо-
         жет стоять  даже комментарий!  Конструкция IFNB WAITFLAG сообщает
         MACRO о необходимости ассемблирования следующих трех строк только
         тогда, когда аргумент WAIT-FLAG не пуст. В противном случае прог-
         раммный код включен не будет, и первой ассемблируемой строкой бу-
         дет одна из строк блока IFIDN.  Это предоставляет нам возможность
         генерации программного кода,  который или будет включаться в ито-
         говую программу, или после проверки ключей будет опускаться. Опе-
         ратор IFNB проверяет существование WAITFLAG не по спеллингу,  та-
         ким образом мы можем вызвать макро одной из следующих команд:

            @WritToFil WAIT,EKO
            @WritToFil WAITE,EKO
            @WritToFil NoWate,EKO
            @WritToFil FOOBAH,EKO

         и затем генерировать код, который "не ждет" ввода. Заметим также,
         что мы получили вложенность макро, когда одно макро вызывает дру-
         гое.

                        Несколько слов о возможностях макро

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

            @WritToFil MAСRO   WAITFLAG,EKOFLAG
                    LOCAL    bye     ;;определить формальный адрес
                    ;;макро приема символа из стандартного ввода
                    ;;2 аргумента: WAITFLAG и EKOFLAG определяют,
                    ;;ждать символ или отобразить ввод
                    .ХCREF  ;;подавить выдачу перекрестных ссылок
                                     ;;локальных меток и т.д.
                    x = 0            ;; х - индикатор
                    IFNDEF  DEBUG    ;;если параметр DEBUG не опре-
                    x = 1            ;;делен, установить флаг в 1
                    ENDIF            ;;конец проверки условия
                    IFNB <WAITFLAG>  ;;если поле для WAITFLAG
                    x = 2            ;;не пусто, флаг = 2
                    ENDIF            ;;конец проверки условия
                    IF (х EQ 1) or (x eq 2) ;;если не определен
                                ;;DEBUG или не пуст WAITFLAG
                    @ChkChr     ;;проверить, ожидается ли символ
                    cmp     al,0  ;;al = 0 => символ не ожидается
                    je      bye   ;;если символа нет, продолжить
                    ENDIF       ;;конец проверки условия
                    IFIDN <EKOFLAG>,<EKO>   ;;если EKOFLAG = EKO,

                                      - 1-54 -
                                ;;ассемблировать следующую строку
                    mov     ah,06h  ;;функция DOS по записи в
                                    ;;стандартный вывод
                    ELSE            ;;в противном случае
                      IFIDN <EKOFLAG>,<NOEKO>  ;;если EKOFLAG=NOEKO,
                                          ;;ассемблировать
                      mov   ah,40h    ;;функция DOS по записи в файл
                      ELSE       ;;если аргумент не соответствует
                        .ERR     ;;выдать ошибку ассемблирования
                    %OUT Ошибка в макро @WritToFil - EKOFLAG не найден
                  ENDIF             ;;конец проверки условия
                ENDIF               ;;конец проверки условия
                int       21h       ;;вызов DOS
            bye:
                .CREF     ;;восстановить выдачу перекрестных ссылок
                ЕNDМ                ;;конец макро


            Теперь во время ассемблирования для определения  режима  DEBUG
         мы можем использовать опцию /d:
            MASM  myprgm,,,; /dDEBUG
         и все  вызовы макро WritToFil будут генерировать программный код,
         проверяющий ввод.
            Для определения,  ждем ли мы появление символа,  мы используем
         флаг (с оператором =,  а не equ,  Так как мы переопределяем его в
         следующих двух операторах IF).  Вместо (x eq 1 ) или (x eq 2)  мы
         могли  бы  закодировать x gt 0 или x NE 0,  тaк как действительно
         любое значение,  отличное от задаваемого при  инициализации  (0).
         Заметим,  что мы также добавили несколько новых директив. Символы
         ;; сообщают MASM, что комментарии не должны появляться в листинге
         ассемблера. Директива .ХСREF экономит время ассемблирования и па-
         мять для листинга перекрестных ссылок, сообщая MASM, что не нужно
         загромождать этот листинг именами,  используемыми только в макро.
         Директива .СREF восстанавливает выдачу  перекрестных  ссылок  для
         оставшейся части листинга.  Кроме того,  ее можно и не указывать.
         Мы также добавили директиву %OUT, которая будет выводить на экран
         вставленное в нее сообщение об ошибке.  Теперь мы поэксперименти-
         руем с некоторыми дополнительными возможностями.

                          Макро, вызывающее подпрограммы

            Одно из наиболее мощных применений макро заключается в универ-
         сальном  вызове подпрограмм аналогично вызовам подпрограмм в язы-
         ках высокого уровня.  Задача заключается в проталкивании несколь-
         ких параметров в стек и вызове подпрограммы.  Довольно просто, за
         исключением потребности макро приспособиться к  переменному числу
         параметров, которые в свою очередь могут иметь переменные размеры
         (байт,слово, двойное слово, четвертное слово и 10-байтовые значе-
         ния  с  плавающей точкой).  Для выполнения этих требований мы ис-
         пользуем операторы .TYPE и TYPE (обратите внимание на точку перед
         первым оператором). Использование оператора .TYPE позволяет макро
         поддерживать как регистр типа BX,  так и слово или  байт  данных.
         Конструкция .TYPE х возвращает байт,  набор битов которого содер-
         жит следующую информацию:


                                      - 1-55 -
            БИТ 0 = 1, если х программно зависимо, иначе = 0
            БИТ 1 = 1, если х зависимо от данных, иначе  = 0
            БИТ 5 = 1, если х определено, иначе          = 0
            БИТ 7 = 1, если х - внешний параметр, если локальный
                                    или общий            = 0
         Все остальные биты нулевые.
            Например, если  х  зависимо от данных,  определено и локально,
         оператор.TYPE х возвращает значение 001000100b (или 22h);  -- ус-
         тановлены  биты  1 и 5.  Так как мы хотим разрешить использование
         регистров (программно зависимых) в качестве параметров,  мы будем
         применять оператор .TYPE для сообщения о наличии параметров,  за-
         висящих от данных. Так как мы хотим поддерживать данные различной
         длины отдельно,  мы используем оператор TYPE,  возвращающий длину
         их аргументов в байтах. Например,

            TYPE N =  1, если N  - байт
            TYPE N =  2, ecли N  - слово
            TYPE N =  4, ecли N  - двойное слово
            TYPE N =  8, если N  - четверное слово
            TYPE N = 10, если N  - десятибайтовое слово (т.е. с плаваю-
                                   щей  точкой)
            TYPE N = XX, если N  - cтруктура длиной в хх байтов
            TYPE N = FFFF,если N - "близкая" программная метка
            TYPE N = FFFE,если N - "удаленная" программная метка

            Следующее макро иллюстрирует использование директив TYPE и
            .TYPE:

            @FcnCall MACRO  Fnctn,ParmList  ;;список подпро-мм и парам-ов
                   IRP     N,<ParmList2>    ;;неопределен. повторение
                   BYTELENGTH = TYPE N      ;;получить длину "проталкива-
                              ;;емых" элементов в байтах
                     IF ((.TYPE N ) NE  22H)     ;;N определено и зави-
                                                 ;;симо от данных?
                     push  N ;;если нет - предположить 16-битовый регистр        ;;
                     ELSE    ;;в противном случае предположить данные
                       IF (BYTELENGTH EQ 2) ;;тогда, если параметр 2-
                                            ;;байтовый,
                     push N      ;;протолкнуть
                     ELSE        ;;в противном случае
                       IF (BYTELENGTH EQ 1)  ;;если параметр 1-байтовый,
                                  ;;предположить, что AX доступен
                       mov      ah,0  ;;очистить верхнюю часть AX
                       mov      al,N  ;;сделать  параметр словом
                       push     ax  ;;так, чтобы мы могли продвинуть его
                       ELSE         ;;в противном случае
                         IF (BYTELENGTH EQ 4) ;;если параметр 4-байтовый
                         push    word ptr N     ;;продвинуть 1-ое и
                         push    word ptr N + 2  ;;2-ое слово
                         ELSE      ;;в противном случае
                           IF (BYTELENGTH EQ 8) ;;если параметр 8-байт.
                           push  word ptr N     ;;продвинуть 1-ое,
                           push  word ptr N + 2   ;;  2-ое
                           push  word ptr N + 4   ;;  3-ье и
                           push  word ptr N + 6   ;;  4-ое слово
                           ELSE         ;;в противном случае
                             IF  (BYTELENGTH EQ  10)   ;;если параметр
                                             ;;10-байтовый, продвинуть

                                      - 1-56 -
                             push word ptr N   ;; 1-ое
                             push word ptr N + 2       ;; 2-ое
                             push word ptr N + 4       ;; 3-ье
                             push word ptr N + 6       ;; 4-ое  и
                             push word ptr N + 8       ;; 5-ое слово
                             ELSE
                             .ERR
                             ENDIF
                           ENDIF
                         ENDIF
                       ENDIF
                     ENDIF
                   ENDIF
                 call Fnctn
                 ENDM                               ;;конец IRP
                 ENDM                               ;;конец макро


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

            @FcnCall Fcn1,<word1,word2,byte3>
            @FcnCall Fcn2,<word1,byte3>

            Для любого вызова подпрограммы мы можем иметь фактически неог-
         раниченное число параметров.
            В данном макро имеется много недостатков. Одним из этих недос-
         татков является то,  что мы не  покрыли  все  возможные  значения
         BYTELENGTH,  типа программных меток и структур;  мы предположили,
         что регистр AX доступен только для однобайтового параметра и т.д.
         Для большинства этих недостатков существует дилемма: цикл, на ба-
         зе BYTELENGTH, мог бы поддерживать все возможные длины данных, но
         при  этом  могли  возникнуть другие проблемы,  поэтому мы даже не
         рассматриваем альтернативы, а считаем своей задачей лишь "протал-
         кивание" данных в вызываемую подпрограмму!  Пример служит для ил-
         люстрации директив TYPE и .TYPE,  однако рассмотрение общецелевой
         функции  вызова  подпрограмм  требует нечто большего.  Прежде чем
         продолжить разбор этого макро мы сделаем короткое  отвлечение  на
         введение понятия структуры.

                            Применение директивы STRUC

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


                                      - 1-57 -
            MathNumbers      STRUC
            BooLean1         DB         (0)     ; 1 байт
            BooLean2         DB         (0)     ; 1 байт
            ShortInteger1    DW         (0)     ; 1 слово
            ShortInteger2    DW         (0)     ; 1 слово
            LongInteger1     DD         (0)     ; 1 двойное слово
            LongInteger2     DD         (0)     ; 1 двойное слово
            Float1           DT         (0)     ; 1 10-байтовое слово
                                                ; (для 8087)
            Float2           DT         (0)     ; 1 10-байтовое слово
                                                ; (для 8087)
            MathNumbers      ENDS

            MathNumbers определяет тип структуры. STRUС и ENDS ограничива-
         ют начало и конец описания структуры.  Теперь мы можем  использо-
         вать MаthNumbers для объявления некоторых данных,  например, так:

            TrueFalse   MathNumbers    <1,0,,,,,,>
            MaxMinShort MathNumbers    <,,32767,-32768,,,,>
            MaxMinLong  MathNumbers    <,,,,2147483647,-2147483648,,>
            e           MathNumbers    <,,,,,,,2.718281828>

            ListLength  = 100
            MathList    MathNumbers    ListLength dup <,,,,,,,>

            Память зарезервирована под 104 числа.  При 34 байтах под число
         наш список чисел займет 3536 байтов.  Первоначально  структура  в
         своем описании инициализируется в 0,  а затем в секции данных ус-
         танавливается в различные значения.  Структуры  могут  рассматри-
         ваться  как директивы данных,  определяемые пользователем.  Имена
         элементов структуры преобразуются MASM в побайтовое смещение  от-
         носительно начала структуры.  Теперь ссылаться на числа структуры
         Вы можете по имени поля точно так, как это делается в Си или Пас-
         кале. Например,

            сmр       MaxMinShort.ShortInteger1,ax

            что эквивалентно

            cmp       [MaxMinShort + 2],ax

            В качестве примера,  если мы хотим просмотреть весь список чи-
         сел в поисках первого числа с плавающей запятой меньше 0, следует
         написать:

                      mov      di,MathList ;получить адрес списка
                      mov      cx,ListLength ;длина списка для зацикл-я
                      mov      bx,(TYPE TrueFalse) ;длина структуры
            CmpLup:   cmp      [di].Float1,0 ;число с ПЗ > 0?
                      jl       ExitLup  ;если нет, искать
                      add      di,bx    ;указатель на др.структуру
                      loop     Cmplup   ;просмотреть весь список эл-ов
            ExitLup:...

                  Адресация к данным во множественных структурах

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

            MathNumbers          STRUC
            Float1                DT    (0)   ;1 10-байтовое слово
            Float2                DT    (0)   ;1 10-байтовое слово
            ShortInteger1         DW    (0)   ;1 слово
            ShortInteger2         DW    (0)   ;1 слово
            LongInteger1          DD    (0)   ;1 двойное слово
            LongInteger2          DD    (0)   ;1 двойное слово
            Boolean1              DB    (0)   ;1 байт
            Boolean2              DB    (0)   ;1 байт
            LibraryPtr            DD    (?)   ;1 двойное слово
            MathNumbers          ENDS

            В нашем случае преимущество использования имен структур в том,
         что после реассемблирования всей программы и элементов данных но-
         вое описание структуры [di].Float1 по-прежнему будет указывать на
         первое  число с плавающей запятой,  хотя мы и реорганизовали дан-
         ные.  Таким образом, программный код, который ссылается на данные
         по именам структур,  не требует корректировки.  Заметим,  однако,
         что если данные файла используют  старые  описания  структур,  мы
         должны перегруппировать существующие данные так,  чтобы они отве-
         чали новой структуре.  Реорганизация структуры не перегруппировы-
         вает существующие данные,  для них лишь объявляется относительное
         местоположение.  Мы должны убедиться,  что действительные  данные
         соответствуют объявлению структуры данных.
            В отличие от структур языка Си структуры MASM не могут  содер-
         жать  описания  других структур (для этого нет особых причин,  и,
         вероятно, в более старших версиях MASM это ограничение будет сня-
         то). Однако нет причины, чтобы структура не могла содержать адрес
         другой структуры,  вот почему мы  включили  в  структуру  элемент
         LibraryPtr.  Предположим, что у нас есть структура Library, опре-
         деленная так:

            Library STRUC
            FloatLib        DD      (0)    ;указатель на библ. с ПЗ
            ShortIntLib     DD      (0)    ;указатель на библ. с коро-
                                           ;ткими целыми
            LongIntLib      DD      (0)    ;указатель на библ. с длин-
                                           ;ными целыми
            ВooleanLib      DD      (0)    ;указатель на библ. с бу-
                                           ;левыми значениями
            Library ENDS

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

            АddLibs Library  <FloatAdd,ShortAdd,LongAdd,BooleanAdd>
            Sublibs Library  <FloatSub,ShortSub,LongSub,BooleanSub>
            MultLibs Library <FloatMult,ShortMult,LongMult,BooleanMult>
            .
            .
            .

                                      - 1-59 -
            Такая комбинация структур может быть использована  как показа-
         но в следующем программном сегменте:

            lds        si,MathList[bx] ;адрес конкретной структуры
            push       ds    ;сохранить адрес структуры данных
            push       si
            lds        si,LibraryPtr ;адрес адресов библиотеки
            call       [ds:si].LongIntLib    ;переход на выполнение
                                             ;операции

            Cоответствующие указатели загружаются в структуру или во время
         ассемблирования,  или во время выполнения программы. Прелесть ис-
         пользования адреса структуры для передачи в подпрограммы парамет-
         ров  и  указателей  в том,  что вызывающий программный код всегда
         один и тот же, несмотря на количество изменений структуры, прово-
         димых в течение всей жизни программы.  Помещая в структуру указа-
         тели на другие структуры данных,  мы избавляем программный код от
         необходимости знания деталей о данных и/или вызываемых операциях.
         Такое "сокрытие данных" развито и более часто используется в объ-
         ектно  ориентированных  языках типа С++ или Smalltalk,  однако Вы
         можете добиться почти того же самого,  применяя только структуры.
         Применить определенную Вами структуру можно также и к набору дан-
         ных,  который создан не Вами.  Например, получить доступ к первым
         22 байтам PSP (префикс программного сегмента), которые MS-DOS по-
         мещает в начало выполняемых файлов,  можно через следующую струк-
         туру:

            PSP    STRUC
            INT32         DB     2 DUP (?)    ; 2 байта
            MemSize       DW     (?)          ; 1 слово
            Reserved      DB     (?)          ; 1 байт
            DOSCall       DB     5 DUP (?)    ; 5 байтов
            TermVctr      DW     2 DUP (?)    ; 2 слова
            BreakVctr     DW     2 DUP (?)    ; 2 слова
            ErrorVctr     DW     2 DUP (?)    ; 2 слова
            PSP      ENDS

            Получить доступ к PSP можно при помощи следующего программного
         фрагмента:


            mov    di,0   ; PSP начинается со смещения 0
            push   cs     ; сегмент PSP в cs
            pop    ds     ; сегмент PSP -> ds
            mov    si,[di].MemSize  ; размер памяти программы ->
                                    ; экстра сегмент

                        Структуры как параметры подпрограмм

            Мы ввели структуры,  как способ решения  задачи  по  написанию
         универсальной процедуры вызова подпрограмм. Разрешите вернуться к
         этой проблеме. Наилучший вариант передачи параметров подпрограмме
         - через адрес структуры.  В качестве примера передадим данные на-
         шей подпрограмме в одном из элементов списка чисел, определенного
         при  рассмотрении  структур.  Адреса всегда состоят из сегмента и
         смещения. Таким образом, макро, вызывающее подпрограмму и переда-
         ющее параметры, будет выглядеть так:

                                      - 1-60 -

            @FcnCall  MACRO  Fnctn,StrucAddr   ;адрес подпрограммы и
                                               ;структуры
                     push   offset  StrucAddr
                     push   segment StrucAddr
                     call   Fnctn

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

            @JmpShort  MACRO  destin
                    db        0EBh  ;1-ый байт команды перехода
                    n = destin - *  ;вычислить расстояние перехода
                    IFE       (n LE 255)  ;в байт поместится?
                      db      n           ;расстояние перехода
                    ELSE
                      .ERR         ;выдать сообщение об ошибке
                      %OUT Ошибка в макро @JumpShort.
                    ENDIF          ;конец проверки условия
                    db         90h   ;3-ий байт команды
                                     ;короткого перехода
                    ENDM

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

                                    Заключение

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


              Глава 2. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ 2: ПРОЕКТИРОВАНИЕ
                          И РЕАЛИЗАЦИЯ МОДУЛЬНЫХ ПРОГРАММ


             Принципы модульного программирования
             Реализация модульных программ на языке Ассемблер
             Типы кодирования
             Интерфейс с языками высокого уровня
             Назначение  и использование локального ЗУ в памяти
             Заключение


             В главе  1  обсуждение было сфокусировано на средствах струк-
         турного программирования и их применении в  среде макроассемблера
         MASM. В главе 2 представлены методы структурного программирования
         и их применение в среде MS-DOS и микропроцессоров 8086/8088.
             Кроме этого, представленный материал содержит еще две отдель-
         ные темы.  Эти темы связаны с проектированием модульных  программ
         на  языке  Ассемблер и реализацией этого проектирования путем ис-
         пользования  макроассемблера  MASM,  макроопределений  и   прочих
         средств,  относящихся к этим проблемам. Обе темы затрагивают осо-
         бенности написания,  наглядность (удобство чтения),  надежность и
         удобство  сопровождения прикладных программ.  Короче говоря,  эти
         методы вместе и отдельно могут быть использованы для структуриро-
         вания прикладных программ с целью повышения их качества.

                       Принципы модульного программирования

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


                                      - 2-2 -
                               Опции проектирования

             Модульное проектирование и декомпозиция относятся  к процессу
         расчленения  больших  проблем  на более узкие,  более управляемые
         подпроблемы.  Первым шагом проектирования является решение, в ка-
         ком месте должна быть граница между этими подпроблемами.
             Для получения максимальных преимуществ от  использования  мо-
         дульного  программирования   каждая подпроблема или модуль должны
         иметь один вход и один выход.  В этом случае можно легко отслежи-
         вать  поток  управления в программе.  В любом месте модуля должна
         иметься возможнось увидеть точку входа в  модуль  и  сказать:  "Я
         знаю значения регистров X,Y и Z в этой точке, потому что они ука-
         зываются как...",  и затем проследить функционирование модуля без
         тревоги  об  искажении программы.  Один вход обеспечивает возврат
         потока управления в точку вызова при вызове модуля.  По этой при-
         чине,  модульные программы почти всегда выполняются как структуры
         CALL-RET.
             Использование нескольких  предложений  RET в модуле не должно
         нарушать правило одного входа, поскольку все инструкции RET возв-
         ращают  управление в одну и ту же точку.  Точно также,  переход к
         общему RET в конце модуля, не изменяет структуру модуля, а добав-
         ляет  лишь  коды  в модуль и увеличивает его сложность.  С другой
         стороны,  вход или выход из модуля не по этому правилу перечерки-
         вает  наибольшие  преимущества  модульного программирования:  яс-
         ность, удобство сопровождения.
             Имеется исключение из правила входа в модуль.  Это происходит
         при использовании таблицы переходов для реализации потока  управ-
         ления внутри программы. Таблица перехода используется путем "про-
         талкивания" адреса возврата в стек, вычисления индекса требуемого
         адреса  перехода в таблице и выполнения перехода в памяти. Пример
         этого приема показан в листинге  программы  драйвера  устройства,
         приведенной в главе 6.
             При практическом выполнении декомпозиции модулей можно  самим
         найти  некоторое  количество  альтернативных решений.  Прежде чем
         осуществить правильный выбор, необходимо знать альтернативы. Цель
         состоит  в  выборе таких альтернатив,  которые создадут наилучшие
         условия проектирования.

                            Функциональная декомпозиция

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

                  Минимизации количества передаваемых параметров

             Иногда обнаруживается, что после определения модулей програм-
         мы   создано  нечто  громоздкое и неуклюжее.  Это часто случается
         тогда,  когда модули при выполнении возложенных на них задач тре-
         буют доступа к обширному количеству данных.  Чаще всего это легко
         может произойти,  если на модуль возложено выполнение  нескольких
         опций.  Чтобы  знать  состояние программы в данное время,  модуль
         должен принимать очень много различных переменных.  Если это так,
         и  выявлено,  что модуль принимает большое количество параметров,
         необходимо ответить на следующие две группы вопросов:
             Первая: В этом модуле предпринята попытка выполнения несколь-
         ких функций? Требует ли модуль параметры, используемые в не отно-
         сящихся к данному модулю секциях?  Если ответы на эти вопросы по-
         ложительные,  то  необходимо  снова   обратиться   к   дальнейшей
         сегментации этого модуля.
             Вторая: Модуль представляет собой функциональный разрез?  Яв-
         ляются ли на самом деле вызывающий и вызываемый модули частью од-
         ной и той же функции? Если это так, то поместите их вместе в один
         модуль, даже если результирующий модуль окажется слишком большим.
         Затем попробуйте выполнить сегментацию  модуля  снова  различными
         способами.
             Сегментация модулей через функциональный разрез часто  проис-
         ходит тогда,  когда программист обнаруживает, что две программные
         секции идентичны или сильно похожи друг на друга. Программист за-
         тем  пытается создать из них один модуль.  Это не модульное прог-
         раммирование,  поскольку результирующий модуль имеет не  функцио-
         нальное соединение.
             Если в процессе проектирования будет обнаружено,  что  ничего
         сделать нельзя,  чтобы избежать использования большого числа ссы-
         лок на данные или передачи меток параметров,  вернитесь обратно в
         начало проектирования и проверьте корректность поставленной проб-
         лемы.

                    Минимизации количества необходимых вызовов

             Одним из существенных преимуществ модульного программирования
         является  то,  что  программа  основного уровня очень часто может
         быть сконструирована для чтения как последовательность вызываемых
         процедур.  Этот факт существенно повышает "понимаемость" програм-
         мы,  поскольку читатель может познакомиться с ее основным потоком
         и  функционированием  после  прочтения  только одной-двух страниц
         программного кода.  Однако   эта особенность может также иметь  и
         недостатки. Одна из многих верхних статистических оценок програм-
         мирования говорит о том, что 90% времени выполнения типовых прог-
         рамм расходуется в 10 % кода программы. При этом подразумевается,
         что если эти 10 %  содержат большое количество цепочечных вызовов
         процедур, то суммарное время, затрачиваемое на управление  выпол-

                                      - 2-4 -
         нением программы,  может стать непреодолимым препятствием на пути
         использования этого подхода.
             Прежде чем отказаться от модульности проектируемой программы,
         проверьте,  что скрывается под зависимостью программы от времени.
         Во-первых, большинство программ затрачивают большую часть времени
         выполнения на ожидание ввода информации с клавиатуры. После нажа-
         тия клавиши требуемые функции, с точки зрения выполнения длитель-
         ного процесса, обычно не расходуют время. Различие между 100 мик-
         росекунд и 100 миллисекунд для среднего пользователя неразличимо.
             Противоположным для некоторых мнением является то,  что дейс-
         твующий механизм пары CALL - RET не перекрывает потребляемое вре-
         мя. По сравнению с инструкциями перехода инструкция CALL выполня-
         ется на 30-50% дольше, а RET в среднем длиннее на 1 цикл.  Только
         когда во внимание принимаются накладные расходы передачи парамет-
         ров,  сохранения регистров и т.д., называемые служебными расхода-
         ми,   модульные  программы  начинают выглядеть медленнее по срав-
         нению с немодульными программами. В дополнение к тому, что модули
         модульных программ обычно являются более общими,  чем их неструк-
         турированные дубликаты, модули модульных программ могут использо-
         вать ссылки на память или стек с большей частотой. Дополнительное
         время,  расходуемое на вычисление действительного адреса  в  теле
         модуля,  может привести к замедлению выполнения конкретного моду-
         ля, чем узко закодированная конкретная программа.
             Преимущества служебных программ  и программ общего назначения
         заключаются в том, что модуль может быть использован виртуально в
         некотором  месте  программы.  При написании немодульной программы
         программист может потратить несколько часов, пытаясь открыть: ис-
         пользуется ли регистр,  или хуже того, верно ли то, что он должен
         использоваться. При модульном программировании программист не ин-
         тересуется тем,  какие регистры он использует в настоящий момент,
         пока вызываемый модуль копирует его параметры в стек  и сохраняет
         весь  набор  регистров на входе.  Эти особенности создают возмож-
         ность сначала использовать приемы модульного программирования для
         повышения  скорости кодирования и затем переработки программы для
         удаления "узких" мест.
             Для областей,  чувствительных к скорости работы, лучшей реко-
         мендацией является выбор основной ветви  программы.  Если  модуль
         упоминается только в чувствительной к скорости работы программной
         секции,  то он может быть включен в "ветвь"  внутри   вызывающего
         модуля.  Если другие секции используют модуль,  то они могут быть
         скопированы в вызывающий модуль в необходимое место.  В  связи  с
         тем,  что  основной вызывающий модуль станет большим,  необходимо
         вставить комментарии в его тело, помечающие включаемый модуль как
         блок его владельца.  Будущие читатели смогут затем прочитать ком-
         ментарии для определения функций модуля и пропустить его мимо для
         возобновления чтения основного кода.

                        Правила модульного программирования

             Наиболее существенные  концепции  модульного программирования
         можно изложить в виде следующих правил:
             - Разделение и подчинение.  Разделите проблему на узкие функ-
         циональные задачи, каждая из которых независима от других, исклю-
         чая необходимые для нее параметры;
             - Один вход - один выход.  Модуль должен  иметь  только  одну
         точку  входа,  в  которую передается управление при всех вызовах.
         Возвращать управление модуль должен в ту точку в программном  по-
         токе,  из  которой было передано управление (адрес возврата может

                                      - 2-5 -
         быть модифицирован,  как обсуждается в следующем разделе по пере-
         даче параметров);
             - "KISS-принцип"(Keep It Simple,Stuiped - делай  все попроще,
         дурачок). Избегайте сложности при кодировании модуля. Используйте
         сложную логику только при условии хорошего документирования, объ-
         ясняющего каждый шаг и способ его проектирования;
             - Упрятывание подробностей. Ограничивайте подробности исполь-
         зования регистров, структуры локальных данных и т.д. для внутрен-
         них модулей. Не допускается реализацию модуля перебрасывать в ос-
         тавшуюся часть программы;
             - Если модуль использует  особую  переменную,  сделайте  так,
         чтобы  переменная была документируемым параметром. Документируйте
         все действия, чтобы модуль имел глобальные данные;
             - Планируйте  обнаружение  ошибок и действия,  которые должны
         быть  предприняты при возникновении  ошибки.  Ответственность  за
         обработку  исключительных ситуаций,  как известно,  должна возла-
         гаться на конкретные модули. Обычно модули нижнего уровня переда-
         ют отчеты об ошибках в вызывающий модуль. Ответственность за при-
         нятие решений по этим ошибкам обычно  резервируется  за  модулями
         верхнего уровня.

                               Справочная литература
             То, что представлено в данном разделе,  является кратким вве-
         дением в концепции  структурного  программирования  и  модульного
         проектирования.  За неимением места,  мы не могли полностью обсу-
         дить данный предмет.  Однако,  по этой проблеме доступно огромное
         количество литературы.  Если Ваша цель состоит в том, чтобы стать
         профессионалом в области программного обеспечения,  то купите не-
         которые  из этих книг и прочитайте их.  Представленные ниже книги
         являются классическими работами по данному  предмету  и  отражают
         узкие  примеры  превосходных  работ на доступном профессиональном
         уровне:
             DeMarco, T. Structured Analysis and System Specification. New
               York: Yourdon,1978.
             Kane,G.,D.Hawkins and  L.Leventhal.68000  Assembly   Language
               Programming,Berkeley:Osborne/McGraw-Hill,1981.
             Tausworthe, R.C.   Standardized   Development   of   Computer
               Software.  Part  1.  Englewood Cliffs,  N.J.:Prentice-Hall,
               1977.
             Yourdon, E.U.,   and   L.L.Constantine.   Structured  Design.
               Englewood Cliffs, N.J.:Prentice-Hall,1977.
             Yourdon, E.U.  Techniques  of  Program  Structure and Design.
               Englewood Cliffs, N.J.:Prentice-Hall,1975.

                 Реализация модульных программ на языке Ассемблер

             До сих пор мы вели абстрактный разговор о  модулях,  передаче
         параметров  и других подобных термах.  Теперь наступило время на-
         чать рассмотрение отношения этой информации  к  конкретному  миру
         языка Ассемблер в среде MS-DOS,  макроассемблера MASM и микропро-
         цессора 8086.
             Модули в  среде MASM лучше всего поддерживаются с помощью ди-
         рективы PROC. Мы будем использовать ее все время в качестве мето-
         да определения точек входа и выхода  программы.  Теперь расширим
         ее  использование  для определения  границ  конкретных  модулей.
         Директива PROC используется MASM для определения метки в програм-

                                      - 2-6 -
         ме,  поэтому дадим этой метке либо атрибут near (близкий) или ат-
         рибут far (далекий).  Этот атрибут используется для генерации как
         правильных типов инструкции CALL, так и правильных типов инструк-
         ции RET. Подробное представление этих типов инструкций приводится
         далее  в  разделе "Типы кодирования".  Здесь нас прежде всего ин-
         тересует то, что директива PROC является удобным способом обозна-
         чения блока программы с одним уникальным входом и постоянным  вы-
         ходом, которые образуют основу модуля.

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

             Мы "вращались"  вокруг  слов параметр,  аргумент и переменная
         подобно шарикам для настольного тенниса. Большей частью эти слова
         имели  взаимозаменяемые  значения.  Теперь  необходимо определить
         различия между ними (хотя некоторые из них несомненно будут  тре-
         бовать уточнения).  После этой главы мы будем всегда возвращаться
         к правильным способам,  однако, сейчас необходимо пояснить основ-
         ные понятия и положения.
             Словарным понятием "параметр" является "элемент характеристи-
         ки".  В  общем смысле параметр представляет собой ссылку на любую
         часть данных, используемую модулем, которая в целом не содержится
         внутри этого модуля.  Почему добавлены слова "ссылка на"? Потому,
         что параметр это не сами данные, и, даже, не адрес данных. Скорее
         всего, параметр это местодержатель (элемент характеристики). Нап-
         ример, рассмотрим уравнение Y+1. Нельзя написать модуль для оцен-
         ки этого уравнения,  потому что Y не является конкретным значени-
         ем!  Y есть параметр, который заменяется действительным значением
         во время оценки этого уравнения.  Действительное значение называ-
         ется "аргументом".
             Мы еще не определили понятие "переменная". Строго говоря, это
         нечто, размещенное в регистре или ячейке памяти и содержащее пор-
         цию данных,  которые могут в дальнейшем изменяться.  В предыдущем
         примере Y также является переменной,  поскольку он  изменяется  в
         зависимости от требуемых обстоятельств.  Поэтому аргументы автома-
         тически являются переменными (но не наоборот).
             Таким образом,  если  объект данных может изменяться,  то это
         переменная.  Если же эта переменная требуется в модуле для выпол-
         нения возложенной  на него задачи,  то она в то же время является
         параметром.  Аргумент - это действительное значение, которое при-
         нимает переменная при вызове модуля.
             Нам также необходимо рассмотреть специальный случай "констан-
         ты".  Константа - это объект данных, значение которого никогда не
         изменяется.  В языке Ассемблер константы могут появляться в  двух
         случаях.  Они  могут быть частью непосредственных данных для инс-
         трукций (например, mov al,4) или они могут быть размещены в памя-
         ти подобно другим данным.  Когда константа помещена в память, она
         отличается от переменной исключительно тем,  что "только  читает-
         ся" и никогда не записывается.
             Может ли параметр также быть константой? Если константа явля-
         ется типом памяти,  то определенно да. Однако, при попытке непос-
         редственного использования константы данных в  качестве параметра
         могут возникнуть определенные проблемы. Непосредственные данные в
         подпрограмме передавать сами себя не могут. Непосредственные дан-
         ные должны содержаться в чем-либо:  в регистре, ячейке памяти или
         в стеке.  В языках высокого уровня о  преобразовании  констант  с
         целью  их размещения заботится компилятор.  В языке Ассемблер это
         должен делать сам программист.

                                      - 2-7 -
                                Параметры и модули

             Мы определили,  что параметры представляют  собой  какие-либо
         данные,  требуемые модулем для выполнения возложенной на него за-
         дачи,  и которые размещаются вне модуля. Мы также определили, что
         параметры определяют и переменные.  Таким образом, вырисовывается
         второе большое преимущество модулей.  В связи с тем, что входными
         данными для модуля являются переменные, то они могут быть измене-
         ны для подходящего конкретного случая.  Тем самым, модулям прида-
         ется  больше общности,  позволяя им быть повторно-используемыми в
         любом месте любой программы.
             В действительности, параметры являются необязательными компо-
         нентами модульного программирования.  Можно иметь модуль, который
         не принимает внешние параметры,  а функционирует исключительно  с
         внутренними данными. Простая программа выработки звукового сигна-
         ла консоли не имеет параметров.  Более  общим  примером  является
         простая  программа для чтения чисел с клавиатуры.  Хотя программа
         чтения числа будет возвращать значение,  программе не нужен ника-
         кой аргумент, передаваемый для нее.
             Объединяя требующиеся  входные параметры и вырабатываемые вы-
         ходные значения, можно сформировать следующие четыре группы моду-
         лей:
             1. Модули, не принимающие входные параметры и не вырабатываю-
         щие выходные значения.
             2. Модули,  принимающие входные параметры и не вырабатывающие
         выходные значения.
             3. Модули,  не принимающие входные параметры и вырабатывающие
         выходные значения.
             4. Модули,  принимающие  входные  параметры и вырабатывающие
         выходные значения.
             Обычно первые две группы модулей,  не вырабатывающие выходные
         данные,  называются подпрограммами, а последние два типа, выраба-
         тывающие выходные данные, функциями. Заметим, что различие произ-
         водится в зависимости от того, требуют ли модули входных парамет-
         ров, хотя как программист вы интуитивно осознаете различие.

                             Опции передачи параметров

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

                              Передача через регистры

             Наиболее общим способом передачи данных  при программировании
         на  языке Ассемблер является способ передачи данных через регист-
         ры. Немедленная доступность и высокая скорость делают их основны-
         ми  средствами  для решения поставленной задачи.  Регистры всегда
         отделены от кода операции, независимо от используемой программной
         среды.  Почти  все вызываемые функции MS-DOS передают свои данные
         таким способом.  Короткие программы языка  Ассемблер,  являющиеся
         интерфейсными программами  с MS-DOS,  для манипулирования данными
         часто используют те  же  самые регистры, которые требуют функции,
         вызывающие эти данные MS-DOS.Это приводит к ощущению создания па-

                                      - 2-8 -
         раметра в том же регистре, что и MS-DOS.
             Одной из трудностей, возникающей при использовании этого спо-
         соба, является то, что количество регистров, имеющееся в наличии,
         ограничено.  Если имеется программа, требующая большее количество
         регистров, чем имеется в наличии, то это вызывает лишние хлопоты.
         Новые микропроцессоры имеют меньшие ограничения,  чем старые,  но
         количество регистров все равно ограничено. Кроме того, если необ-
         ходимо  переместить  часть  программы из одного типа процессора в
         другой, то ситуация, в которой два процессора могут совместно ис-
         пользовать один и тот же набор регистров,  маловероятна. Вам при-
         дется перепроектировать все интерфейсы модулей.
             Другой трудностью является то, что необходимо непрерывно сох-
         ранять используемую дорожку, на которую выводится каждый регистр.
         Эта  игра "кто первый" может наскучить даже наиболее опытному иг-
         року. Особое расстройство вызывает случай, когда принято решение,
         что регистр X освободился и, следовательно, освободилась програм-
         ма модуля.  Позднее,  когда принимается решение об  использовании
         того же самого модуля в другом месте, может оказаться, что не ос-
         вободился как раз только регистр X.  Так команда PUSH  (запомнить
         содержимое регистра в стеке) записывает в стек значение, содержа-
         щееся в регистре X,  затем выполняется вызов и команда POP  (выб-
         рать  значение  регистра из стека) выбирает из стека значение ре-
         гистра и заносит его в регистр X. Таким образом, в результате та-
         кого оборота X содержит возвращаемое значение. Видите, что теперь
         освободилось? И так случается очень часто.
             Практически, ограничением  передачи параметров через регистры
         является ограничение объема информации,  передаваемой  через  ре-
         гистры,  до 16 бит,  т.е. размера наибольшего регистра. В связи с
         тем,  что большинство переменных имеют тенденцию использоваться в
         виде  байтов  или слов,  ограниченный размер регистра не является
         серьезной проблемой. Когда передаваемые данные  превышают  размер
         регистра,  вместо  данных  вызывающая программа может передать их
         адрес в памяти. Конечно, для правильного использования данных вы-
         зывающая программа должна знать, какой был указан тип данных. При
         вызове функций MS-DOS всякий раз,  когда они требуют большого ко-
         личества  данных,  используется этот механизм передачи данных,  с
         заключающимся в указании адреса данных в памяти.

                    Передача данных через общую область памяти

             Следующим способом передачи данных является использование за-
         ранее  подготовленной области памяти.  Понятие "заранее подготов-
         ленная" используется в том смысле,  что и вызывающая и вызываемая
         программы "договорились" между собой о том, что их данные переда-
         ются в некоторую область основной памяти.  Программа А знает, что
         вывела  квитанции  об оплате за последний месяц в область памяти,
         помеченную как FOO, и программа В знает как выглядит эта информа-
         ция  в области памяти FOO.  Поэтому область памяти FOO называется
         общей областью памяти.
             Передача данных через общую область памяти имеет,  по крайней
         мере,  один существенный момент. В пределах физических возможнос-
         тей  используемого  компьютера  можно  передать  столько  данных,
         сколько необходимо.  Передача данных через общую  область  памяти
         позволяет  передавать и принимать целую гамму свободных регистров
         и допускает передачу данных любого размера от буфера в  один байт
         до нескольких килобайт.
             Кроме того, передача данных через общую область памяти делает

                                      - 2-9 -
         доступными передаваемые данные любому модулю, которому они требу-
         ются. Это большое преимущество, т.к. запрашиваемые данные переда-
         ются  от модуля верхнего уровня через большое количество внутрен-
         них модулей к модулю нижнего уровня.  Поэтому,  каждый модуль  не
         должен обрабатывать те данные, которые он не использует.
             Отрицательной стороной этого способа передачи данных является
         то,  что  зависимость от общей памяти может ограничить общность и
         повторность использования модулей.  Рассмотрим ряд модулей, пред-
         назначенных  для  чтения и записи файлов.  Если модули кодируются
         для использования общего блока памяти,  то может возникнуть проб-
         лема  одновременного  открытия  двух файлов.  Если программа была
         спроектирована для выполнения сравнения,  то она должна  скопиро-
         вать  один набор данных из буфера в область памяти для предотвра-
         щения возможной перезаписи буфера.
             Последний недостаток  способа передачи данных через общую об-
         ласть памяти вытекает из его особенности.  В связи с тем, что об-
         ласть памяти доступна любому модулю,  это  типичный  случай "игры
         по правилам". Защита данных от случайного разрушения почти невоз-
         можна.  Обычно  это  не является большим риском (поскольку ошибки
         программы общие),  но становится очень существенным фактором  при
         рассмотрении повторно используемого программирования (рассматрива-
         емого в следующем разделе "Типы кодирования").

                      Передача данных через память программы

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

                              Передача данных в стек

             Передача данных в стек является способом,  используемым боль-
         шинством компиляторов языков высокого уровня для реализации вызо-
         ва  процедур.  При этом способе передачи данных перед выполнением

                                      - 2-10 -
         вызова все требуемые параметры заносятся в стек. После вызова вы-
         зывающая программа осуществляет доступ к данным без их пересылки.
         Проектировщики семейства микропроцессоров  8086  поддержали  этот
         способ  при обеспечении регистра BP (base pointer - указатель ба-
         зы). Регистр BP имеет удивительную особенность адресации его опе-
         рандов относительно сегмента стека.  Это означает,  что при уста-
         новке значения регистра BP  в  правильное  положение,  содержимое
         стека может быть адресовано путем использования индексной адреса-
         ции.
             Что же такое "правильное положение" при загрузке в BP? Это не
         сам SP (stack pointer - указатель стека),  поскольку SP указывает
         на адрес возврата в стек.  Данные обычно начинаются с ячейки SP+2
         или ячейки SP+4. Почему плюс 2, или плюс 4? Потому, что для вызо-
         ва  процедуры  near (близкий) процессор запоминает только текущее
         смещение (указатель инструкции) в стеке (2 байта), в то время как
         для  вызова процедуры far (далекий) процессор запоминает смещение
         и сегмент программы в стеке (4 байта). Вызываемая программа может
         быть  закодирована  для  начала доступа в правильном положении (в
         зависимости от типа программы) при использовании следующей  адре-
         сации:


             NEAR                          FAR
             mov  bp,sp                    mov  bp,sp
             mov  <1-й аргумент>,[bp+2]
             mov                           <1-й аргумент>,[bp+4]
             ...                             ...

             Заметим, что если необходимо  сохранить  содержимое  регистра
         BP, то обычно в этом случае вызываемая программа должна поместить
         BP в стек, изменив адрес первого параметра на [BP+4] для програм-
         мы near и на [BP+6] для программы far. Чтобы избежать это измене-
         ние адресов,  необходимо перед тем,  как  поместить  параметры  в
         стек, передать вызывающей программе ответственность за сохранение
         BP.  Однако,  из-за причин обеспечения совместимости это не реко-
         мендуется.  Вместо этого более предпочтительным способом передачи
         параметров является структура, показанная в листинге 2-1. Исполь-
         зование этой структуры,  заимствованной большинством языков высо-
         кого уровня,  поможет при разработке  мобильных,  многократно ис-
         пользуемых   программ.   Эти   программы  могут  быть  собраны  в
         "инструментальный набор", который необходимо использовать во мно-
         гих местах для облегчения программирования и повышения производи-
         тельности работы.
             При возврате вызываемой программы параметры, которые были по-
         мещены в стек,  теперь должны быть удалены.  Вызывающая программа
         может  удалить параметры либо извлечением из стека (путем исполь-
         зования инструкции POP),  либо просто добавлением хранимых  пара-
         метров  в  регистр SP,  например,  по инструкции add SP,N,  где N
         представляет собой  количество  байтов,  занимаемых  параметрами.
         Этот способ, показанный в листинге 2-1, эффективно урезает стек в
         первоначальное положение. Альтернативно ответственность за очист-
         ку  стека может быть назначена вызываемой программе путем исполь-
         зования инструкции RET N, где N опять количество байтов, занимае-
         мое параметрами.  При любом способе N равно количеству помещенных
         с помощью инструкции PUSH слов, умноженное на 2.
             Различие между  этими двумя способами состоит в том,  что при
         использовании инструкции RET N   программа  должна  вызываться  в

                                      - 2-11 -
         точности  с правильным количеством параметров.  Если имеется не N
         байтов параметров, то инструкция RET N неправильно выровняет стек
         и произойдет авария системы. Напротив, если стек очищает вызываю-
         щая программа путем использования инструкции add SP,N,  то каждый
         вызов  в  целевую программу может передавать различное количество
         параметров.

                      Листинг 2-1. Передача параметров в стек
         -----------------------------------------------------------------
                                ; Вызывающая процедура

              ...          ...
             push      <argument_N>   ; пересылка последнего аргумента
              ...          ...
             push      <argument_2>   ; пересылка   второго   аргумента
             push      <argument_1>   ; пересылка первого аргумента
             call      <myproc>       ; вызов процедуры
             add       sp,<2N>        ; очистка стека
              ...          ...
                                ; Вызываемая процедура

         <myproc> PROC NEAR           ; пример вызова процедуры near
             push      bp             ; сохранение старого BP
             mov       bp,sp          ; указатель ссылки на стек
              ...          ...
             mov       <dummy>,[bp+4] ; доступ к первому параметру
             mov       <dummy>,[bp+6] ; доступ ко второму параметру
              ...          ...
             mov       <dummy>,[bp+2+2N] ; доступ к последнему параметру
              ...          ...
             mov       sp,bp          ; восстановление SP
             pop       bp             ; удаление сохраненного BP
             ret                      ; возврат в вызывающую программу
         <myproc> ENDP
         ----------------------------------------------------------------

             До тех пор,  пока вызывающая программа обрабатывает стек пра-
         вильно,  проблем  не будет.  (Конечно,  если вызываемая программа
         сможет использовать различное количество  параметров,  выдаваемых
         от вызова к вызову).
             С целью облегчения программирования следует  заменить простой
         вызов внешними участками программы, использующими инструкции PUSH
         (записать в стек), MOV (переслать), POP (извлечь из стека) и про-
         чие. Это как раз одно из мест для вывода известных и простых мак-
         росов для выполнения этих рутинных операций. Макросы, приведенные
         в  листинге 2-2,  помогают вызывающей программе поддерживать стек
         во время передачи параметров.  Аналогично, макросы, приведенные в
         листинге 2-3, помогают вызываемой программе при доступе и возвра-
         те параметров из стека.  Все регистры, используемые в этих макро-
         сах, должны быть длиной в слово, потому что инструкции PUSH и POP
         не работают с 8-битовыми регистрами.

                                      - 2-12 -
         Листинг 2-2. Макросы @CallS и @FCallS для передачи параметров стек
         ------------------------------------------------------------------
         ;; **** Макрос @PushIm: запись в стек непосредственных данных
         ;;                                  через регистр BP
         @PushIm MACRO   arg
                 mov     cs:mem_16,&arg
                 push    cs:mem_16
                 ENDM
         ;; **** Макрос  Вызов подпрограммы: @Calls  имя,<arg1,arg2,...>
         @CallS  MACRO   routine_name,arg_list
         ?count  =       0
                 IRP     argn,<&arg_list>
                 push    &&argn          ; передача параметра
         ?count  =       ?count+1
                 ENDM
                 @PushIm %?count         ; передача количества параметров
                 call    &routine_name   ; вызов программы
                 add     sp,2*(1+?count) ; очистка стека
                 ENDM
         ;; **** Макрос  Вызов функции: @FCallS  имя,<arg1,arg2,...>
         @FCallS MACRO   routine_name,arg_list,return_val
         ?count  =       0
                 IRP     argn,<&arg_list>
                 push    &&argn          ; передача параметра
         ?count  =       ?count+1
                 ENDM
                 @PushIm %?count         ; передача количества параметров
                 call    &routine_name   ; вызов программы
                 pop &return_val         ; получение  возвращаемого знач-я
                 if ?count               ; если не нуль ...
                 add sp,2*?count         ; очистка стека
                 ENDIF    ENDM
         -----------------------------------------------------------------
                   Листинг 2-3. Макросы @Accept,@RetVal и @CRet
                 для приема в стек и возврата параметров из стека
         -----------------------------------------------------------------
         ;; **** Макрос  @RetVal: @RetVal регистр
         @RetVal MACRO   return_value      ; возвращаемое значение
                 mov     [bp+4],return_val ; возврат слова
                 ENDM
         ;; **** Макрос  @Accept: @Accept <reg1,reg2,...>
         @Accept MACRO   reg_list
                 push    bp              ; сохранение указателя базы
                 mov     bp,sp           ; уст-ка BP для доступа к парам.
                 mov     &pnum,[bp+4]    ; получение количества парам-ов
         ?count  =       0
                 IRP     reg,<&reg_list>
         ?count  =       ?count+1
                 push    &&reg           ; сохр-е рег-ра для нового знач.
                 mov     &&reg,[bp+4+?count*2] ; получение параметра
                 ENDM
                 ENDM
         ;; **** Макрос  @CRet: @CRet <reg1,reg2,...>
         @CRet   MACRO   reg_list        ; список регистров
                 IRP     reg,<&reg_list>
                 pop     &&reg           ; восст-е сохраненного регистра
                 ENDM
                 pop     bp              ; восстановление указателя базы
                 ret                     ; возврат из программы
                 ENDM

                                      - 2-13 -
             Макрос  @PushIm  позволяет  пользователям   микропроцессоров
         8086/8088 помещать непосредственные данные в стек.  Для использо-
         вания макроса сначала необходимо определить в программном сегмен-
         те местоположение слова mem_16.  Несмотря на то, что передача не-
         посредственных  данных  в  стек  медленная  и принимаются большие
         коды, такой алгоритм работы создает большую свободу использования
         регистров.
             Символ ?count  в  макросах  @CallS и @FCallS используется для
         сообщения вызываемой программе количества  предусмотренных  пара-
         метров;  для приема количества байтов,  помещенных в стек;  и для
         использования при очистке стека после вызова.  Если  целевая  или
         вызываемая  программа уже знает сколько параметров было в нее пе-
         редано (обычно является случайным),  то эти макросы  должны  быть
         модифицированы,  чтобы  обойтись  без передачи и очистки счетчика
         параметров.  Заметим, что счетчик параметров также используется в
         качестве  поля  возврата  значения  для  вызова  функций (макросы
         @FCallS и @RetVal).
             Макрос  @RetVal предназначен  для  использования  с  макросом
         @FCallS  и  замещает счетчик параметров,  помещенный в стек с по-
         мощью макроса @FCallS,  16-битовым значением для возврата в вызы-
         вающую программу.
             Макрос @Accept  целевой  программы  работает  либо с макросом
         @CallS, либо с макросом @FCallS для передачи параметров из стека в
         регистр.  Этот макрос сохраняет регистры,  используемые в процессе
         работы.  Символ ?count используется здесь для определения смещения
         следующего параметра в стеке.  В связи с тем,  что макрос  @Accept
         работает  в  направлении вверх по стеку (увеличение смещения),  то
         этот макрос выбирает параметры из стека в порядке,  обратном тому,
         в котором они были помещены в стек! Заметим также, что оба макроса
         @Accept и @RetVal предполагают  вызов  процедуры  near  (близкий),
         поскольку они допускают только 2-байтовый адрес возврата.
             Последний целевой макрос @CRet восстанавливает регистры,  ко-
         торые были сохранены макросом @Accept.  В связи с тем,  что  инс-
         трукции  POP  должны  быть в обратном порядке по отношению к инс-
         трукциям  PUSH,  список  аргументов  для  макроса  @CRet   должен
         располагаться в порядке,  обратном тому, какой был при выполнении
         макроса @Accept.  Последним действием, предпринимаемым перед инс-
         трукцией RET, является восстановление указателя базы, сохраненно-
         го макросом @Accept.
             Приведенные макросы представлены здесь скорее в качестве при-
         меров, нежели рабочих копий и могут быть улучшены для обеспечения
         более полного использования.  Например,  параметр инструкции PUSH
         (push &&argn) для обработки непосредственных  данных  в  качестве
         параметров может быть замещен более общим макросом PushOp из гла-
         вы 1.  Одним из ограничений текущей версии является то,  что инс-
         трукция  mov [bp+4],return_value в макросе @RetVal не может возв-
         ращать переменные  памяти в стек,  потому что семейство микропро-
         цессоров 8086 не поддерживает инструкцию   пересылки   память-па-
         мять. Для распознавания пересылки память-память и генерации пере-
         дачи через  непосредственный  регистр  этот  макрос  должен  быть
         переделан.
             Кроме того,  необходимо иметь в виду,  что макросы, представ-
         ленные в листингах 2-2 и 2-3, реализуют вызывающую программу, ко-
         торая  несовместима  ни с одним известным языком высокого уровня.
         Характерно, что эти процедуры в качестве дополнительного аргумен-

                                      - 2-14 -
         та  передают  количество  аргументов  для  вызываемой процедуры и
         возвращают значения для вызывающей  процедуры  непосредственно  в
         стек.
             MASM обеспечивает для вызываемой программы  некоторые  средс-
         тва,  упрощающие  доступ  к  данным  в стеке.  Благодаря описанию
         structure (структура),  которая описывает данные в стеке и вырав-
         нивает указатель базы (BP) на начало структуры,  к данным в стеке
         можно обращаться по символическим именам. Это помогает предотвра-
         щать  фатальные ошибки кодирования,  которые являются результатом
         указания неправильного смещения. Листинг 2-4 демонстрирует дирек-
         тиву MASM STRUC в этом контексте.

               Листинг 2-4. Символический доступ к содержимому стека
                                по директиве STRUC
         -----------------------------------------------------------------

                               ; Вызывающая процедура

                  ...        ...
                 push   <argument_1> ; пересылка 1-го аргумента
                 push   <argument_2> ; пересылка 2-го аргумента
                  ...        ...
                 push   <argument_N> ; пересылка последнего аргумента
                 call   <myproc>     ; вызов процедуры
                  ...        ...

                               ; Вызываемая процедура

         StackFrame     STRUC        ; описание шаблона стека
                 dw     ?            ; сохраненный BP
                 dd     ?            ; адрес возврата (используйте "dw"
         ;                             для NEAR (близкий))
         paramN  dw     ?            ; последний параметр
                  ...       ...
         param2  dw     ?            ; 2-й параметр
         param1  dw     ?            ; 1-й параметр
         StackFrame     ENDS         ; конец описания шаблона
         ;
         base    EQU    [bp]         ; база шаблона
         ;
         <myproc> PROC  FAR          ; пример вызова far (далеко)
                 push   bp           ; сохранение старого BP
                 mov    bp,sp        ; указатель ссылки в стеке
                  ...      ...
                 mov    <dummy>,base.param1 ; доступ к 1-му параметру
                 mov    <dummy>,base.param2 ; доступ ко 2-му параметру
                  ...      ...
                 mov    <dummy>,base.paramN ; доступ к последнему пар-ру
                  ...      ...
                 mov    sp,bp        ; восстановление SP
                 pop    bp           ; сброс сохраненного BP
                 ret    (2N)         ; возврат в вызывающую программу
         <myproc> ENDP
         ----------------------------------------------------------------

             Листинги 2-1 и 2-4 различаются по трем важным аспектам.  Пер-
         вое  отличие заключается в порядке помещения параметров в стек. В

                                      - 2-15 -
         листинге 2-1 вызывающая программа размещает свои параметры в сте-
         ке в обратном порядке (от последнего к первому), в то время как в
         листинге 2-4 в прямом порядке  (от  первого  к  последнему).  Для
         структуры  StackFrame при работе с листингом 2-1 порядок парамет-
         ров должен быть изменен на противоположный  (т.е.  реверсирован).
         (Назначение  порядка  "от  первого к последнему" может привести к
         путанице в этой точке. На самом деле назначается порядок следова-
         ния параметров "слева - направо",  т.е.  как они появлялись бы на
         языке высокого уровня).
             Второе отличие  между примерами заключается в способе очистки
         переданных параметров из стека. В примере, приведенном в листинге
         2-1,  вызывающая  программа очищает параметры в стеке посредством
         инструкции add SP,<2N>.  В листинге 2-4 вызываемая программа очи-
         щает стек, используя инструкцию ret (2N).
             Последнее отличие заключается в том, что в листинге 2-1 пока-
         зана программа near (близкий),  в то время как в листинге 2-4 по-
         казана программа far (далекий).  Если структура  StackFram  будет
         использоваться с процедурой near, то необходимо заменить директи-
         ву dd директивой dw.  Это вызывает резервирование в  шаблоне  для
         адреса  возврата  вызывающей  программы только двух байтов,  в то
         время как для вызываемой процедуры far требуется четыре  байта. С
         другой  стороны,  если  структура  будет использована в программе
         прерывания, то  после  директивы dd необходимо будет добавить до-
         полнительную директиву dw для резервирования памяти  для  флажков
         процессора, помещаемых в стек при прерывании.
             Директива STRUC  не  выполняет добавление никакого кода в ко-
         нечную программу.  Эта директива только описывает  смещения,  ис-
         пользуемые с указателем базы BP,  для облегчения задачи обращения
         к параметрам.
             Стек также обеспечивает удобное место для хранения возвращае-
         мых значений, но мы отложим обсуждение этой темы до тех пор, пока
         не  обсудим различия между функциями и подпрограммами в последую-
         щих разделах этой главы.

                    Краткое изложение опций передачи параметров

             Существует три возможных способа передачи данных модулям:
             1. Передача  через  регистры - допускается несколько парамет-
         ров; это наилучший способ для простого интерфейса и для обработки
         исключительных ситуаций, а также для возврата значений.
             2. Передача через общую область памяти  -  ограничивает  гиб-
         кость и общность модулей,  но имеет преимущества обеспечения дос-
         тупности данных для всех модулей.
             3. Передача данных в стек - предпочтительный способ обработки
         данных;  превосходит в общности (многократно-используемые модули)
         и  вырабатывает  модульные программы;  необходим для интерфейса с
         большинством языков высокого уровня.
             Кроме того,  при передаче данных способом,  отличным от выше-
         приведенных, каждый модуль должен принимать в качестве параметров
         необходимые ему данные не только для себя самого, но и для других
         модулей,  которые он вызывает в процессе своей работы. Иногда это
         может  привести к большому списку параметров для модулей верхнего
         уровня.
             В действительности,  по всей вероятности, для организации пе-
         редачи данных модулю может потребоваться использование комбинации
         этих  способов  (за исключением передачи данных в память програм-
         мы).

                                      - 2-16 -

                    Передача параметров по значению или адресу

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

                               Передача по значению

             Чаще всего передача параметров в языке  Ассемблер выполняется
         путем  передачи значения.  При этом способе передачи в вызывающую
         программу передаются действительные данные (их значения). Целевая
         программа получает число,  хранящееся в регистре, либо помещенное
         в стек.
             Хранение  данных  в общей памяти может представлять собой не-
         сколько специальных случаев. В первом случае данные передаются по
         адресу,  поскольку вызывающая и вызываемая программы осуществляют
         обращение к данным посредством значений общих адресов.  В  другом
         случае  данные  в общей области могут быть либо значениями,  либо
         адресами,  и проблема упрощается,  базируясь на решении о природе
         данных в общем блоке. Если данные являются значениями, то они пе-
         редаются по значению.  Если данные являются адресом, то они пере-
         даются по адресу.
             Если параметры, содержащие непосредственные данные, передают-
         ся в стек, то пользователи микропроцессоров 8086 и 8088 не должны
         испытывать страх перед некоторыми дополнительными усилиями,  свя-
         занными с передачей значений в стек.  Пользователи усовершенство-
         ванного микропроцессора 80x86 могут использовать  инструкцию
                     PUSH <immediate>
         (переслать в стек непосредственные данные),  однако  пользователи
         других микропроцессоров должны передавать данные в стек через не-
         посредственный регистр.  Для  этого может быть использован макрос
         @PushIm,  рассмотренный  в  главе 1, однако, для этого приложения
         его сложность не указывается.  Если используется вызывающая прог-
         рамма, приведенная в листинге 2-1, то для передачи непосредствен-
         ных данных в стек доступен регистр BP (указатель базы).  Почти во
         всех соглашениях по архитектуре микропроцессоров  8086  для  этих
         целей предназначен регистр AX. Любые непосредственные данные, ко-
         торые необходимо переслать в стек, передаются с помощью следующих
         двух строк программы:

               mov   ax,<immediate_data>
               push ax

             Способ передачи  параметров по значению унаследовал ограниче-
         ние передаваемого значения при использовании регистра  и передаче
         данных в стек до 16 бит.  На самом деле,  8-битовые данные вообще
         не могут быть помещены в стек.  Конечно же,  имеются пути  обхода
         этого ограничения, примером этого является макрос @PushOp из гла-
         вы 1. Данные, относящиеся к большим структурам, иногда могут быть
         переданы  в стек словами,  но если вызываемая программа не должна
         получать свои параметры из стека,  то передача адреса данных нам-
         ного удобней.

                                      - 2-17 -
                                Передача по адресу
             При передаче  по  адресу вызываемая программа получает только
         адрес данных.  Доступ  к данным осуществляется путем использования
         этого адреса.  Имеется несколько непосредственных преимуществ дан-
         ного способа. Первое состоит в том, что если данные не расположены
         в различных сегментах,  то все адреса могут  содержаться  в  одном
         16-битовом  значении,  являющимся соглашением по использованию ре-
         гистра или стека. Второе преимущество заключается в том, что прог-
         рамма  становится  более общей,  поскольку указание другого адреса
         создает новый набор данных. Третье преимущество состоит в том, что
         вызываемая  программа может непосредственно манипулировать данными
         для возврата значения в то же самое  место  вызывающей  программы,
         которое содержало первоначальное значение. Если данные, подлежащие
         передаче,  не размещены в памяти (т.е.  являются непосредственными
         данными),  иногда могут возникнуть проблемы.  В этих случаях (или,
         если обнаружено их простое несоответствие для передачи всех требу-
         емых  адресов  в стек) может быть использован тип смешанного пара-
         метра: блок аргументов.
             Блок аргументов или параметров является специальной формой пе-
         редачи по адресу.  В этом случае требуемые аргументы содержатся  в
         непрерывном  участке памяти.  Однако,  в отличие от передачи через
         общую область памяти,  вызывающая процедура не имеет полных сведе-
         ний  об  этом блоке.  При вызове процедуры в качестве параметра ей
         передается адрес этого блока.  Хотя может оказаться неудобным раз-
         мещать  все  требуемые аргументы в блоке,  но это дает возможность
         избежать необходимости размещения всех этих значений в стеке. Если
         блок уже существует для других целей, то передача параметров через
         блок аргументов имеет еще больший смысл.

                      Защита целостности передаваемых данных

             Имеется другой аспект опции "передача по ...", который являет-
         ся очень важным и облегчающим использование. Этот аспект относится
         к целостности данных или к их защите от непреднамеренного  измене-
         ния или порчи.  При типовом использовании данные,  передаваемые по
         значению,являются копией действительных данных. Как таковая, вызы-
         ваемая  программа  может манипулировать данными по любому пути без
         изменения данных в вызывающей программе.  С другой  стороны,  если
         вызываемая программа принимает адрес данных, по которому программа
         затем может изменять данные,  возможно изменение  функционирования
         вызывающей программы. Данные, передаваемые по значению, рассматри-
         ваются затем как защищенные,  а данные,  передаваемые  по  адресу,
         рассматриваются как подвергаемые риску.
             Удивительно, что переменные,  передаваемые в регистр,   иногда
         рассматриваются как передаваемые по адресу,  поскольку  регистры в
         аппаратных средствах являются простыми специализированными   адре-
         сами.  Это различие делается потому,  что данные в регистрах  под-
         вержены риску, если подпрограмма или функция изменят данные в  ре-
         гистре и это повлияет на основную программу.
             Относительно степени подверженности данных постороннему   воз-
         действию  отсутствуют  жесткие и прочные правила.   Такие понятия,
         как "передача по значению" и "передача по  адресу"   могут  помочь
         оценить ситуацию, но действительное решение по использованию  типа
         передачи зависит от того,  насколько ценны данные для   вызывающей
         программы (степень риска), и имела ли доступ вызываемая  программа
         к  первоначальным данным.  Это  в действительности определяет  на-
         сколько большая защита требуется для этих данных.

                                      - 2-18 -

                       Функции в сравнении с подпрограммами

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

                           Возврат значений в регистрах

             Простейшим способом возврата значения является способ возвра-
         та значения в регистре.  Как и при передаче параметров, эта опция
         может  быть ограничена количеством доступных регистров и размером
         возвращаемых данных. Положительным моментом этого способа возвра-
         та является то, что данные легко доступны и могут быть легко про-
         верены или использованы.
             Возврат значения  в регистре имеет смысл для часто вызываемых
         функций.  Это не требует специальной установки и  предварительной
         подготовки  буферов  и пр.  Большинство функций MS-DOS возвращает
         свои значения этим способом. Однако, если все функции в программе
         возвращают свои данные через регистры,  то придется столкнуться с
         задачей "большой бухгалтерии и  перемешивания".  Кроме  этого,  в
         связи с тем, что регистры являются элементами, в которых произво-
         дится большинство вычислений,  налицо жесткая конкуренция  по  их
         использованию.
             Чаще всего регистры должны использоваться для небольших, час-
         то используемых вызываемых программ,  возвращающих немного значе-
         ний и для программ,  возвращаемые значения которых должны  немед-
         ленно  подвергаться  вычислениям.  Одним из примеров этого случая
         могла бы быть функция чтения символьных значений и преобразования
         их в числовые значения.
             Большинство языков высокого уровня для  возвращаемых значений
         используют различные технические приемы. Так, для возврата байто-
         вого значения или значения,  длиной в слово,  обычно используется
         регистр AX. Если необходимо возвратить значение, длиной в двойное
         слово,  такое как указатель far (далекий),  то  младшее  значащее
         слово (или часть смещения) возвращается в регистре AX,  а старшее
         значащее слово (или часть сегмента) возвращается в регистре DX. В
         тех  случаях,  когда в вызывающую программу необходимо возвратить
         более двух слов, данные помещаются в буфер памяти, а указатель на
         этот буфер возвращается в вызывающую программу.  Способы управле-
         ния этим указателем зависят от конкретного языка.

                         Возврат значений в общей области

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

                             Возврат значений в стеке

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

                        Отчеты об исключительных ситуациях

             Здесь рассматривается индикация о состоянии возврата  или  об
         обнаруженных и выдаваемых ошибках.  Во многих прикладных програм-
         мах одной из требуемых опций является необходимость иметь вызыва-
         емые процедуры,  функции и подпрограммы, обеспечивающие некоторые
         типы индикации об ошибках или кодах состояния. Вероятно, читатель
         уже заметил,  что большинство функций MS-DOS вызывает возврат ко-
         дов состояния о завершении.  Часто для индикации  наличия  ошибки
         используется  бит переноса в одном или более регистрах,  обычно в
         регистре AX, содержащем подробную информацию о типе ошибки.
             Бит переноса используется для номера причины.  Его легко про-
         верить (с помощью инструкций JC - переход,  если был перенос, или
         с помощью инструкции JNC - переход, если не было переноса); легко
         установить,  дополнить или очистить (с помощью инструкций  STC  -
         установка флажка переноса, CMC - дополнение флажка переноса и CLC
         - сброс флажка переноса);  а также можно сохранить и восстановить
         (с  помощью инструкций PUSHF - запоминание флажков в стеке и POPF
         - извлечение флажков из стека).  Доступ к флажку  переноса  более
         совершенен  чем доступ к любому другому биту состояния в архитек-
         туре микропроцессоров 8086/8088.  Это сочетание обеспечивает иде-
         альный  механизм  для  индикации наличия исключительной ситуации.
         Конечно,  программист должен помнить об очистке бита переноса для
         индикации  нормального завершения в случае,  если ошибка не прои-
         зошла,  потому что бит переноса может быть уже установлен при вы-

                                      - 2-20 -
         полнении обычной операции.
             После того,  как вызывающая программа обнаружит, что возникла
         ошибка, программа должна установить природу ошибки. Иногда после-
         дующая информация не требуется. Если требуется дополнительная ин-
         формация, то для полного кода полезен выделенный регистр. Логично
         выбрать регистр AX, но в связи с тем, что от этого регистра зави-
         сит так много других операций (например,  MUL - умножение аккуму-
         лятора на операнд и DIV - деление аккумулятора  на  операнд),  он
         может  оказаться недоступным.  Какой бы регистр не выбирался,  он
         должен содержать не только код ошибки, но также и код нормального
         завершения. В случае, если первичная информация об ошибке потеря-
         на, программа может повторно проверить регистр для получения сос-
         тояния завершения. Если информация критическая, выберите значение
         для нормального завершения, которое является ненормальным резуль-
         татом.  Это означает,  нельзя использовать значение нуля для нор-
         мального завершения,  потому что другая ошибка может легко почис-
         тить  код состояния.  MS-DOS обеспечивает обслуживание отчетов об
         ошибках для  использования  с  программами,  выполняющими  другие
         программы.  Если  подпроцесс  хочет вернуть код ошибки в процесс,
         который вызывал этот подпроцесс,  он  может  поступить  так,  как
         часть  функционального  вызова процесса завершения - функция 4Сh.
         Затем порождающий процесс может получить этот код  возврата через
         функцию MS-DOS 4Dh. Затем можно получить код возврата порожденно-
         го процесса. Этот механизм используется только с программами, вы-
         полняемыми  под  управлением функции 4Bh - функции загрузки и вы-
         полнения программы.

                                 Типы кодирования

             Для большинства основных программ на любом языке программиро-
         вания  программист  редко  интересуется  подробностями выполнения
         программы процессором.  Подробности обработки в/в, управления па-
         мятью,  размещения программы в памяти при ее выполнении, как пра-
         вило,  предоставляются для управления операционной системе. Одна-
         ко,  имеется ряд моментов, когда требуется более непосредственное
         управление программной средой.  В эти моменты программисту  может
         потребоваться  знание  и готовность принять ответственное решение
         по вопросам механизма загрузки,  размещения и выполнения програм-
         мы.  Примерами, когда это требуется, могут служить: написание ав-
         тономных программ,  функционирующих без присутствия MS-DOS;  под-
         держка  оверлейных  (перекрываемых)  программ  для  использования
         больших программ в ограниченной физической  памяти;  и  написание
         драйверов прерывания или рекурсивных программ.
             Во время выполнения программы размещение ее в памяти  отража-
         ется двумя путями. Во первых, для связи счетчика программы (также
         называемого как указатель инструкции) или адреса ссылки  памяти с
         блоком  физической  памяти используются регистры сегмента.  Затем
         внутри этого блока формируется действительная  ссылка,  используя
         смещение от начала этого блока. Это смещение появляется в счетчи-
         ке программы,  в ссылках на память и внутри косвенных  ссылок  на
         память через регистры.
             Что это означает для выполнения программ с  различными типами
         кодирования?  Эти  типы ссылок и случаи,  когда они используются,
         определяют как программа загружается в память,  какие особенности
         она  может использовать и как программа может быть структурирова-
         на. Рассмотрим как создаются эти ссылки и как их использовать для
         создания более совершенных программ.

                                      - 2-21 -

                       Размещение программного кода в памяти

             Понимание альтернатив размещения программного кода  в  памяти
         требует  ясного понимания работы как инструкций управления выпол-
         нением программы (CALL - вызов процедуры, RET - возврат из проце-
         дуры  и JMP - безусловный переход), так и доступа к памяти микро-
         процессора 8086, так как это прежде всего ограничивает возможнос-
         ти программиста при размещении программы в доступном пространстве
         памяти.
             Инструкции управления  выполнением  программы  часто называют
         инструкциями передачи управления,  которые включают две  основные
         инструкции  CALL  -  вызов процедуры и JMP - безусловный переход.
         Каждый случай, когда программа начинает выполнение с нового места
         в памяти, называется "пунктом назначения". Каждая из этих функций
         имеет три опции реализации для указания пункта  назначения. Этими
         опциями являются: текущее относительное размещение, адресация от-
         носительно текущего сегмента и абсолютная адресация.

                             Относительное размещение

             Текущее относительное размещение иногда называют  относитель-
         ным  PC (program counter) счетчиком программы,  который вычисляет
         адрес пункта назначения от текущего адреса и смещение. Для форми-
         рования  адреса пункта назначения смещение добавляется к текущему
         размещению. В связи с тем, что полная операция в целом не зависит
         от абсолютного расположения программного кода в памяти, результи-
         рующий адрес имеет независимое размещение.  Если в памяти пересы-
         лается целый блок программы, то созданный скорректированный адрес
         пункта назначения указывает на новое положение  инструкции пункта
         назначения.
             Этот способ вычисления адреса передачи используется  во  всех
         инструкциях условного перехода,  во всех внутрисегментных (корот-
         ких или близких) инструкциях JMP (безусловный переход) и  во всех
         внутрисегментных  (близких)  инструкциях  CALL (вызов процедуры).
         "Непосредственная" означает, что инструкция (JMP или CALL) содер-
         жит смещение как непосредственные данные.  Напротив,  "косвенная"
         (непрямая) инструкция - это инструкция (JMP или CALL) для адреса,
         содержащегося  в  16-битовом регистре (только смещение),  или для
         адреса,  содержащегося в 16-битовой или 32-битовой ячейке  памяти
         (смещение или смещение и сегмент).  В связи с тем, что прямые пе-
         редачи управления не включают действительные адреса, то они могут
         быть  размещены  в  памяти где угодно и даже могут быть пересланы
         внутри сегмента, пока исходные инструкции (JMP и CALL) и програм-
         ма пункта назначения пересылаются совместно.

                     Адресация относительно текущего сегмента

             Адресация относительно  текущего сегмента указывает на значе-
         ние действительного смещения для загрузки в  указатель инструкции
         (как  при  косвенной инструкции CALL - вызвать процедуру) или для
         использования в качестве указателя  данных.  Ссылки,  выполняемые
         этим способом, всегда указывают на ту же самую ячейку внутри бло-
         ка памяти,  адресуемого с помощью соответствующего регистра  сег-
         мента. Как таковые, программные коды или данные не могут быть пе-
         ресланы внутри сегмента.  Однако,  такие программные  коды  могут
         быть  пересланы  в памяти,  если регистр сегмента для этого блока

                                      - 2-22 -
         также изменен. Т.к. сегменты должны быть выровнены на границу па-
         раграфа (шестнадцатиричный адрес XXXX0), то программный код может
         быть переслан только путем прибавления 16 байтов (один параграф).
             Этот тип адресации используется внутри сегментными (близкими)
         косвенными инструкциями JMP - безусловный переход и CALL  - вызов
         процедуры,  где новое значение указателя инструкции пункта назна-
         чения выбирается из регистра или  ячейки  памяти.  Эта  адресация
         также  используется  во  всех ссылках на данные независимо от ис-
         пользуемого сегмента (DS,  ES или SS). Код, использующий этот тип
         ссылок,  рассматривается  еще как переместимый,  пока обновляются
         регистры сегмента для отражения позиции программного кода.

                               Абсолютная адресация

             Абсолютная адресация выполняется в тех  случаях,  когда  явно
         указывается  адрес  физической памяти.  Для выполнения абсолютной
         адресации в семействе микропроцессоров 8086 необходимо явно  ука-
         зать  адрес сегмента и смещение.  Эта ссылка каждый раз указывает
         на одну и ту же ячейку памяти.  Абсолютная адресация в  микропро-
         цессоре 8086 используется редко. Только несколько инструкций мик-
         ропроцессора 8086 имеют способность генерировать абсолютные адре-
         са.  Этими  инструкциями  являются  : внутрисегментные  (далекие)
         инструкции JMP - безусловный переход и CALL - вызов  процедуры, а
         также инструкции LDS и LES (загрузка указателя,  используя DS или
         ES).  Инструкции JMP и CALL (непосредственные или косвенные)  об-
         новляют  не только смещение (указатель инструкции),  но и регистр
         сегмента кода (CS).  Он указывает физический адрес памяти. В свою
         очередь,  инструкции LDS и LES не только загружают смещение в 16-
         битовый регистр,  но и загружают  либо  регистр  сегмента  данных
         (DS), либо регистр дополнительного сегмента (ES). Опять это физи-
         ческий адрес памяти.
             Другим способом  создания абсолютного адреса является исполь-
         зование инструкции MOV - пересылка и POP - извлечь из  стека  для
         непосредственной загрузки константы в один из регистров сегмента.
         Заметим однако,  что значение,  пересылаемое с помощью инструкции
         POP в регистр CS, в процессорах iAPX186, iAPX188, или iAPX286 не-
         допустимо и не должно выполняться, если только по причине совмес-
         тимости.

                              Типы программного кода

             При обсуждении свойств программы рассматривался тип сложности
         ее адресации.  Если программа содержит лишь одну абсолютную ссыл-
         ку,  то такая программа называется программой, имеющей абсолютную
         адресацию, или неперемещаемая. Ее нельзя перемещать в памяти.
             Внимательные читатели  могут  подумать  о  том,  что допущена
         ошибка.  В конце концов точка входа в  программу  макроассемблера
         (MASM) указывается как far (далекий) - и все. Выполнимые програм-
         мы с расширением .EXE загружают регистры DS и  ES  по  инструкции
         MOV - переслать. Оба этих факта, как кажется, подразумевают непе-
         реместимую программу,  но операционная система  MS-DOS  выполняет
         загрузку  программы  в память по различным адресам как требуется.
         Ключом к этой дилемме является то,  что используемые значения  не
         являются константами в MS-DOS.  Макроассемблер MASM и компоновщик
         LINK обращаются с именами сегментов и именем процедуры far (дале-
         кий)   специальным   способом   обработки,   который   называется
         relocation map (схема настройки). При загрузке программы в память
         MS-DOS читает схему настройки и изменяет значения тех ссылок, ко-
         торые содержат адреса сегментов.  Для программистов  важно  заме-
         тить,  что MS-DOS не расширяет это правило для стандартных значе-
         ний данных,  и загрузка одного из регистров  сегмента  константой
         это  не  то же самое,  что использование имени сегмента или имени
         процедуры far (далекий).

                                 Переместимый код

             Макроассемблер MASM  и  компоновщик  LINK обычно вырабатывают
         перемещаемые программы.  Т.е.  при нормальном  использовании  они
         создают такие программы, которые могут быть перемещены в памяти с
         помощью MS-DOS,  и при этом правильно функционировать.  Изменится
         только содержимое регистров сегментов.  Это свойство используется
         множеством прикладных программ.  Одни программы  могут  загружать
         другие программы,  используя функцию 4Вh (полезную для оверлейных
         - перекрываемых программ). Несколько программ могут быть загруже-
         ны  в  память одновременно (полезно для мультизадачных систем или
         программ резидентной памяти, таких как, например, программы пред-
         варительной подкачки данных для печати).
             Как уже указывалось,  MS-DOS выполняет эту возможность  путем
         изменения только значений регистров и таких мест в программе, ко-
         торые ссылаются на имя сегмента или имя процедуры  far (далекий).
         Можно  также  расширить эти концепции гибкости на области данных,
         используемые программой. Обычно перемещаемые программы содержат и
         перемещаемые  области  данных.  Когда  загрузчик  MS-DOS помещает
         программу в память, он назначает значения для всех ссылок на сег-
         менты раньше ссылок на программные сегменты.  Листинг 2-5,  полу-
         ченный из программного файла .EXE стандартного  типа,  показывает
         ссылку на сегмент данных, используемую для загрузки регистра сег-
         мента данных. Листинг 2-6 показывает эквивалентную программу, по-
         лученную с помощью макроассемблера MASM.

              Листинг 2-5. Исходный код для заголовка программы .EXE
         -----------------------------------------------------------------

         data_seg SEGMENT              ; определение сегмента данных
                  ...                  ; значения и области данных

         data_seg ENDS
         code_seg SEGMENT              ;  определение кодового сегмента
                  ASSUME cs:code_seg
                  ASSUME ds:data_seg
         main     PROC   FAR           ; точка входа в программу
         start:
                  mov    ax,data_seg   ;  передача адреса сегмента данных
                  mov    ds,ax         ; ... в AX и оттуда в ...
                  mov    es,ax         ; ... регистры сегмента
                  ...     ...
         -----------------------------------------------------------------

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

                 Листинг 2-6. Листинг для заголовка программы .EXE
         -----------------------------------------------------------------

         0000                           code_seg SEGMENT
                                                 ASSUME cs:code_seg
                                                 ASSUME ds:data_seg
         0000                           main     PROC   FAR
         0000                           start:
         0000 B8 ---- R                          mov     ax,data_seg
         0003 8E D8                              mov     ds,ax
         0005 8E C0                              mov     es,ax
         -----------------------------------------------------------------

                             Отдельные области данных

             Если в программе определяется несколько сегментов данных (ис-
         пользуя,  соответственно,  директивы ASSUME), то внутренние прог-
         раммы могут иметь отдельные области данных. Но, при обычном стиле
         программирования каждый раз при вызове программы каждая программа
         ограничена доступом к одной и той же области данных. Область дан-
         ных предназначена для программы и наоборот.
             При обычном использовании назначенные области не являются по-
         мехой,  так как большинство программ выполняется последовательно,
         одна за другой. Но, что произойдет, если попытаться выполнить од-
         ну  и  ту же  процедуру более одного раза и в одно и то же время?
         Будет или нет более поздний вызов  перезаписывать  предшествующие
         вызовы данных,  из-за того,  что программа использует только одну
         область данных? Здесь можно удивиться, почему одна и та же проце-
         дура будет вызываться более одного раза одновременно?
             По крайней мере,  это возможно  в  трех  случаях.  Во-первых,
         мультизадачные  системы  могут  иметь множество выполняемых прог-
         рамм, разделяющих общие библиотеки программ, называемые библиоте-
         ками  исполнимых  модулей (потому что программный код доступен во
         время исполнения,  а не включается в программу во время компонов-
         ки).  Вместо наличия нескольких копий внутренних программ, разме-
         щенных в программном файле,  библиотеки исполнимых модулей  имеют
         только одну копию программы, размещенной в памяти (для более под-
         робного обсуждения библиотек исполнимых модулей смотри  главу 3).
         Если бы даже все они могли выполнять одну и ту же программу в од-
         но и то же время,  библиотеки исполнимых модулей должны  были  бы
         иметь отдельные области данных,  чтобы избежать неумышленное сов-
         местное использование и порчу данных.
             Второй случай, когда одна и та же процедура может быть вызва-
         на программами одновременно,  происходит  в  системах  управления
         прерываниями.  Допустим,  что  выполняется  некоторая программа и
         произошло прерывание из-за некоторого внешнего события.  Програм-
         ма, обслуживающая прерывание, начинает выполнение и ей необходимо
         вызвать программу,  которая была прервана.  Если она не имеет от-
         дельной области данных, то программа обслуживания прерывания раз-
         рушит данные, относящиеся к прерванной программе. По этой причине
         программам обслуживания прерываний необходимо иметь отдельные об-
         ласти данных.

                                      - 2-25 -

                               Рекурсивные программы

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

              Листинг 2-7.  Решение проблемы рекурсии для вычисления
                                    факториала
         -----------------------------------------------------------------

         factor  PROC NEAR              ; нахождение факториала числа N
                 cmp  ax,2              ; уже достигнут конец?
                 jne  subfact           ; нет, вычисление (N - 1)!
                 mov  ax,2              ; да, выполнение сначала
                 ret
         subfact:
                 push ax                ; сохранение текущего значения N
                 sub  ax,1              ; получение N - 1
                 call factor            ; запрос (N - 1)!
                 pop  bx                ; восстановление значения N
                 mul  bx                ; N x (N [min]-1)! = N!
                 ret
         factor  ENDP
         -----------------------------------------------------------------

           Повторно-входимый код - необходимое условие локальной памяти

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

                             Локальная память в стеке

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

               Листинг 2-8. Использование стека для локальной памяти
         -----------------------------------------------------------------

                           ; Вызывающая процедура

                 ...    ...
                push   <argument_3> ; передача 3-го аргумента
                push   <argument_2> ; передача 2-го аргумента
                push   <argument_1  ; передача 1-го аргумента
                call   Example      ; вызов процедуры
                add    sp,6         ; очистка стека
                 ...    ...

                           ; Вызываемая процедура

         StackFrame    STRUC       ; определение шаблона структуры стека
         LocWord dw    ?           ; локальная переменная Word (слово)
         LocChar db    14 dup (?)  ; локальный массив символов
         LocIndx dw    ?           ; другая локальная переменная Word
         XamplBP dw    ?           ; сохраняемое значение BP
                 dw    ?           ; адрес возврата (NEAR call)
         Param1  dw    ?           ; 1-й параметр (передан последним)
         Param2  dw    ?           ; 2-й параметр
         Param3  dw    ?           ; 3-й параметр (передан первым)
         StackFrame    ENDS        ; конец определения шаблона
         ;
         base    EQU   [bp - offset XamplBP] ; выравнивание BP с шаблоном
         ;
         Example PROC  NEAR        ; начало процедуры
                 push  bp          ; сохранение старого указателя базы
                 mov   bp,sp       ; выравнивание Stackframe со стеком
                 sub   sp,offset XamplBP ; резервирование памяти в стеке
                 push  si          ; сохранение используемых регистров
                 push  di
                 ...   ...                        ...
                 mov   si,base.Param1  ; доступ к 1-му параметру
                 mov   al,base.LocWord ; доступ к локальным переменным
                 ...   ...                        ...
                 pop   di          ; восстановление сохраненных регистров
                 pop   si          ;
                 mov   sp,bp       ; удаление локальных переменных
                 pop   bp          ; восстановление первоначального BP
                 ret               ; возврат без очистки
         Example ENDP              ; конец процедуры Example (пример)

                                      - 2-27 -

             Т.к. структура  Stackframe  определяется  в текущем сегменте,
         нет необходимости перекрывать сегмент. Если используется смещение
         из другого сегмента, как например, при попытке использования шаб-
         лона из другого сегмента данных, то в ссылке необходимо использо-
         вать  конструкцию SS:  override (заместить).  Отказ от выполнения
         этого требования приводит к выдаче сообщения об ошибке в макроас-
         семблере  MASM  "Can't  reach with segment reg" (невозможно найти
         регистр сегмента). Если когда-либо появится это сообщение, то оно
         означает, что для определения сегмента, к которому осуществляется
         доступ,  текущий сегмент не принят во внимание и нужно решать эту
         проблему.
             Если  в стеке распределяется локальная память,  то перед воз-
         вратом управления  из программы она должна быть освобождена.  Это
         можно выполнить путем добавления размера локальной памяти к стеку
         (возвращение к прежнему состоянию sub sp,offset bp или восстанов-
         ление регистра BP из сохраненного значения (mov  sp,bp)).  Память
         можно  не  освобождать с помощью инструкции RET N,  т.к.  текущая
         вершина стека не содержит адрес возврата!
             В большинстве компиляторов с языков высокого уровня  предпоч-
         тительным способом хранения локальных данных является использова-
         ние этой "временной" памяти в  стеке.  Переменные,  помещаемые  в
         этот тип памяти,  иногда упоминаются как локальные,  динамические
         или автоматические переменные.  Листинг 2-8 представляет типичную
         последовательность событий,  происходящих на входе типичных прог-
         рамм на языке высокого уровня. Процедура устанавливает новый блок
         данных  (сохранение регистра BP и установка регистра BP в текущем
         SP),  распределяет локальную память (вычитает из SP) и  сохраняет
         регистры, которые она может разрушить.
             Рис. 2-1 представляет структуру стека, как она выглядит внут-
         ри программы Example (пример), и показывает как шаблон Stackframe
         (блок данных стека) выравнивается вместе со стеком.  Заметим, что
         выравнивание   выполняется   благодаря    описанию    базы    как
         "[BP-offset XamplBP]". Т.к. XamplBP выровнен с ячейки сохраненно-
         го BP в стеке,  то выбранное описание  base.XamplBP  эквивалентно
         [BP  -  offset XamplBP + offset XamplBP],  а последнее есть то же
         самое,  что [BP + 0].  Другим важным моментом  является  то,  что
         структура шаблона стека должна начинаться с объявления таких эле-
         ментов, которые будут размещаться в нижней памяти.

                Инструкции ENTER и LEAVE для локальной памяти стека

             В более старших моделях семейства микропроцессоров 8086 фирмы
         "Интел"  обеспечены две новые инструкции для помощи при использо-
         вании локальной памяти в стеке. Все процессоры iAPX186, iAPX188 и
         iAPX286 поддерживают инструкции ENTER (вход) и LEAVE (выход). Ин-
         струкция ENTER используется для установки локальной памяти в сте-
         ке, когда программа вводится впервые, а инструкция LEAVE освобож-
         дает эту локальную память,  когда программа  осуществляет  выход.
         Дополнительные  инструкции  ENTER  и LEAVE имеют возможность под-
         держки указателей блоков,  которые используются определенным бло-
         ком структурированного языка высокого уровня,  например,  такого,
         как язык программирования Паскаль.
             В связи со сложностью этих инструкций,  здесь представлены их
         макросы,  эквивалентные листингу 2-9.  Это позволит пользователям
         микропроцессоров  8086/8088 прочувствовать преимущество этих инс-

                                      - 2-28 -
         трукций при переходе к более современному микропроцессору.  Обра-
         тите внимание на то, что макросы enter и leave отклоняются от не-
         официального стандарта отсутствием в качестве префикса  знака  @,
         потому  что они предназначены для замены инструкций ENTER и LEAVE
         при использовании микропроцессоров 8086/8088.
             При выполнении  инструкция  ENTER  осуществляет  в  стеке три
         действия.  Она всегда помещает значение регистра BP в стек.  Если
         значение level (уровень) больше или равно 1,  то инструкция копи-
         рует предыдущие значения регистра BP в стек.  Если значение local
         (локальный)  больше  или равно 1,  то инструкция открывает прост-
         ранство для локальной памяти в  стеке  путем  вычитания  значения
         local из старого BP в стеке (первая инструкция push).

          Старшие адреса .----------------------------------.-----
                         | Предыдущие структуры Stackframe  |   ^
                         |----------------------------------|   |
                         |             Param3               |   |
                  [BP+8] |----------------------------------|   |
                         |             Param2               |   |
                  [BP+6] |----------------------------------|   |
                         |             Param1               |   |
                  [BP+4] |----------------------------------|   |
                         |                                  |   |
                  [BP+2] |----------------------------------|   |
                         |             XamplBP              |Stackframe
                  [BP+0] |----------------------------------|   |
                         |             LocIndx              |   |
                  [BP-2] |----------------------------------|   |
                         |             LocChar              |   |
                  [BP-4] |----------------------------------|   |
                         |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\|   |
                                                                |
                          /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\    |
                  [BP-16]|----------------------------------|   |
                         |             LocWord              |   v
                  [BP-18]|----------------------------------|-----
                         |      Сохраненное значение SI     |
                         |----------------------------------|
                         |      Сохраненное значение DI     |
           Вершина стека |----------------------------------|
                         |          Доступная память        |
          Младшие адреса  ----------------------------------

               Рис.2-1. Локальная память стека и доступ к параметрам

             Инструкция LEAVE выполняет действия, обратные действиям инс-
         трукции ENTER, пока регистр BP оставлен в, или сброшен в первона-
         чальное значение, установленное инструкцией ENTER.
             Наиболее запутанной фазой этой операции является ее  связь  с
         указателем блока данных.  На рис.2-2 показано состояние (и содер-
         жимое) стека для ряда операций, которые состоят из четырех после-
         довательных инструкций ENTER.
             Каждый элемент стека на рис.2-2  отображает  в  символической
         форме 2 байта (по этой причине все значения параметров local (ло-
         кальный) для инструкции ENTER умножаются на 2 байта. Это не явля-
         ется ограничением инструкции ENTER). Стрелки на рис. 2-2 в симво-
         лической  форме  отображают  ссылку  одного  элемента  на  другой

                                      - 2-29 -
         элемент (т.е. элемент содержит адрес другого элемента).

              ENTER 4,1           ENTER 2,2           ENTER 4,3
              xxxxxxxx            xxxxxxxx            xxxxxxxx
          .-->[старое BP]<---BP#1 [старое BP]         [старое BP]
          ----[FP # 1   ]  ^      [FP # 1   ]         [FP # 1   ]
              [local    ]  |      [local    ]         [local    ]
         SP-->[local    ]  |      [local    ]         [local    ]
                           |  .-->[BP # 1   ]<---BP#2 [старое BP]
                            --|---[FP # 1   ]  ^      [FP # 1   ]
                               ---[FP # 2   ]  |      [FP # 2   ]
                            SP--->[local    ]  |      [local    ]
                                               |  .-->[BP # 2   ]<--BP#3
                                               |  |   [FP # 1   ]
                                                --|---[FP # 2   ]
                                                   ---[FP # 3   ]
                                                      [local    ]
                                                 SP-->[local    ]

                    Рис.2-2. Действие инструкции ENTER в стеке

             Первая инструкция ENTER (уровень 1) устанавливает  единствен-
         ный указатель блока данных,  указывающий на свой собственный блок
         данных, и открывает верхнее пространство в стеке для 4 байтов па-
         мяти.  Вторая инструкция ENTER (уровень 2) не только создает свой
         собственный указатель блока (FP#2), но и копирует указатель блока
         данных  из  предыдущего  блока  данных (FP#1).  Вторая инструкция
         ENTER создает только 2 байта локальной памяти. Последняя инструк-
         ция  ENTER  (уровень 3) переносит шаг 1 операции дальше,  копируя
         указатели блока предыдущих двух уровней (FP#1 и FP#2).
             Почему выполнение примера последовательно начинается с уровня
         1 инструкции ENTER,  а не с инструкции ENTER уровня 0?  Уровень 0
         инструкции  ENTER просто помещает содержимое регистра BP в стек и
         вычитает значение local (локальный) из указателя стека,  устанав-
         ливая  регистр  BP для указания на только что помещенное значение
         регистра BP. Указатели блока данных не копируются. Уровень 0 инс-
         трукции  ENTER является идеальным для создания локальной памяти в
         стеке.  При использовании инструкции ENTER  вместе  с  директивой
         STRUC,  инструкция  ENTER может почти автоматически создавать ло-
         кальную память стека, которая легко доступна.

                 Листинг 2-9. Эквиваленты макросов для инструкций
                                   ENTER и LEAVE
         -----------------------------------------------------------------
         ;; МАКРООПРЕДЕЛЕНИЯ ДЛЯ ИНСТРУКЦИЙ ENTER И LEAVE
         ;;
         ;; Описания базовой адресации для использования при доступе
         ;; к элементам в блоке стека, созданном инструкцией ENTER
         ;;
         pbase equ     [BP + 4]           ;; доступ к параметрам
         lbase equ     [BP - ??tsize]     ;; доступ к локальным данным
         fbase equ     [BP - ??fsize]     ;; доступ к указателю блока
         ;; Form  ENTER     local <immediate 16>, level <immediate 8>
         ;;
         ;; ENTER-- Создание блока стека и распределение локальной памяти
         ;; Копирование указателя блока стека из предыдущей программы в
         ;; новый блок стека для этой программы и открытие пространства

                                      - 2-30 -
         ;; в стеке для новой локальной памяти
         ;;
         enter  MACRO   local,level
                ??tsize = local + level * 2
                ??fsize = level * 2
                push    bp
                IF (level NE 0)
                  IF (level GT 1)
                    REPT level - 1
                      sub       bp,2
                      push      [bp]
                    ENDM
                  ENDIF
                    mov bp,sp
                  IF (level GT 1)
                    add bp,(level - 1) * 2
                  ENDIF
                  push bp
                ELSE
                  mov   bp,sp
                ENDIF
                sub     sp,local
                ENDM
         ;; Form LEAVE
         ;;
         ;; LEAVE-- Выполнение процедуры возврата, удаляющей блок стека
         ;; и локальной памяти, установленной по инструкции ENTER
         ;;
         leave  MACRO
                mov    sp,bp
                pop    bp
                ENDM
         -----------------------------------------------------------------

             На листинге 2-10 показан фрагмент программы создания  локаль-
         ной памяти в стеке с использованием инструкции ENTER.  Этот фраг-
         мент программы определяет,  распределяет и  использует  локальную
         память из стека.  Инструкция ENTER способствует обучению резерви-
         рования необходимого количества памяти благодаря  оператору  MASM
         SIZE.  Знак  процента  (%) требуется только с реализацией макроса
         инструкции ENTER.  При использовании  версии  машинной  программы
         (поддерживаемой  макроассемблером  MASM 2.0 и выше путем указания
         переключателя .286С) знак процента(%) должен быть опущен.

             Листинг 2-10. Создание и ссылка к локальной памяти стека
                                по инструкции ENTER
         -----------------------------------------------------------------

         ?data_1      STRUC
         my_var dw    ?
         ?data_1      ENDS
         test   PROC  NEAR
                ENTER %(size ?data_1),0  ; распределение локальной памяти
                mov   lbase.my_var,10    ; запоминание значения в л.п.
         -----------------------------------------------------------------

                                      - 2-31 -

             Символ lbase  в  листинге 2-9 определен как базовый адрес для
         доступа ко всем  локальным  переменным.  Действительной  ссылкой,
         создаваемой в инструкции MOV, является:

                          mov   [BP - ??tsize].my_var,10

             Символ ??tsize устанавливается реализацией макроса инструкции
         ENTER в количества  байтов,  добавляемых  в  стек  по  инструкции
         ENTER,  не  включая значение регистра BP.  Значение этого символа
         вычисляется как local + level * 2. При вычитании значения символа
         ??tsize  из  содержимого регистра BP,  результатом является адрес
         верхней части стека.  Таким образом,  все ссылки структуры  имеют
         положительное  смещение от символа lbase.  Даже если используется
         версия машинного кода инструкции ENTER, можно легко написать мак-
         рос,  который  вычисляет  значение символа ??tsize и создает инс-
         трукцию ENTER таким образом, что этот механизм может быть с успе-
         хом использован на процессорах 186/188/286.
             Другим символом, определенным в листинге 2-9, является символ
         pbase  - базовый адрес для доступа ко всем переменным, переданным
         в стек.  Значение символа pbase равно [BP + 4],  чтобы охватить 2
         байта, помещенные в стек как часть near (близкий) инструкции CALL
         (вызвать процедуру) и 2 байта, требующиеся для регистра BP, поме-
         щенного  в стек по инструкции ENTER.  После того,  как определена
         структура параметров стека,  символ pbase можно  использовать  по
         имени  его  поля  для символического доступа к данным,  например,
         pbase.my_param.
             Получив описание простого использования инструкции ENTER, вер-
         немся к вопросу об указателях блока данных.  Что это такое? Каждый
         указатель  блока данных указывает на начало блоков данных предыду-
         щей программы стека.  Путем загрузки регистра BP содержимым одного
         из  указателей блока данных,  размещенного в текущем блоке,  может
         быть получен доступ к  локальным  переменным  предыдущего  уровня.
         Первоначально  это было спроектировано для реализации языков прог-
         раммирования высокого уровня таких  как,  например,  Паскаль,  где
         программа  имеет  автоматический  доступ  к переменным порождающей
         программы.  Если читатель недостаточно созрел в отношении высокого
         уровня структурного программирования на языке Ассемблер, то он ве-
         роятно  пропустит  использование  возможностей   указателя   блока
         инструкции ENTER.  Во всяком случае,  если читатель решил испробо-
         вать использование инструкции ENTER с указателем блока,  то незна-
         чительные  эксперименты  дадут  ему  возможность прочувствовать ее
         функционирование.

                  Краткое изложение размещения программного кода

             Заметим, что  повторная  входимость  не  является  необходимым
         условием переместимости программ, и что переместимость не является
         необходимым условием  повторной  входимости  программ.  "Настройка
         программ"  (путем  модификации адресов при размещении программы по
         определенному  адресу)  применяется  для  возможности  перемещения
         программы  в памяти.  "Повторная входимость" применяется для прог-
         рамм,  имеющих "безопасную" локальную память.  "Рекурсивные" прог-
         раммы -  это тип повторно-входимых программ с ослабленными ограни-
         чениями,  когда программист знает в какой точке должны быть сохра-
         нены данные при подготовке следующего вызова.
             Кроме того,  при написании повторно-входимых  программ  нельзя
         забывать о том, что параметры программы также должны быть повторно
         -входимыми.  Когда новая процедура или задача получает управление,
         данные  должны  передаваться в такую область вызываемой программы,
         которая либо всегда защищена (как стек),  либо всегда  сохраняемая
         (например,  все  программы  обслуживания прерываний сохраняют свои
         регистры при вызове программы).
             Также следует помнить о том, что имеется два типа переместимо-
         го программного кода.  К первому типу относится настраиваемая сис-
         тема программ операционной системы MS-DOS, когда MS-DOS, используя
         схему настройки,  изменяет значения переменных сегмента для  того,
         чтобы настроить программу.  Ко второму типу относятся самонастраи-
         вающиеся программы,  для которых не требуется схема настройки. Са-
         монастраивающимися  программами могут быть только такие программы,
         которые  имеют только адресацию смещения в инструкциях  CALL  (вы-
         звать процедуру) и JMP (безусловный переход).

                        Интерфейс с языками высокого уровня

             Наиболее общим использованием языка Ассемблер в настоящее вре-
         мя является применение его в качестве приложения к языку  програм-
         мирования высокого уровня.  При разработке программы, как правило,
         обычно используют язык высокого уровня и лишь небольшую часть  мо-
         дулей пишут на языке Ассемблер. Язык Ассемблер используется тогда,
         когда критичны скорость работы программы или ее размер,  или когда
         язык  высокого уровня не обеспечивает доступ к полным возможностям
         или к аппаратным средствам.
             Имеется три  главных области,  относящихся к связи программ на
         языке Ассемблер  с программами на языке высокого уровня. Это - со-
         гласование имен между двумя модулями;  обработка любых специальных
         установок,  которые могут требовать язык программирования и компи-
         лятор  языка; настройка модулей языка Ассемблер для надлежащей по-
         следовательности вызова и инструмента передачи параметров, исполь-
         зуемых компилятором конкретного языка высокого уровня.
             В прошлом,  для языков высокого уровня  было  достаточно  мало
         правил  регулировки  соглашений о присвоении имен и последователь-
         ностей вызова.  Сегодня ситуация во многом изменилась, т.к. многие
         компиляторы  следуют стандартам Американского национального инсти-
         тута стандартов (ANSI).  В связи с широким использованием компиля-
         торов  языков  высокого уровня фирмы "Майкрософт" и в связи с тем,
         что они придерживаются стандартов ANSI,  оказалось возможным  выб-
         рать компиляторы  фирмы  "Майкрософт"  с  языков программирования:
         Бэйсик, Си, Фортран  и  Паскаль для иллюстрации соглашений  вызова
         подпрограмм.

                 Соглашения о связях для языка Си фирмы "Майкрософт"
             Соглашения о  связях,  проиллюстрированные  в  листинге   2-8,
         представляют типичную программу на языке программирования Си. Если
         бы программу Example перетранслировать на язык Си, то ее начальные
         предложения были бы похожи на следующие:
              void Example (Param1, Param2, Param3)
                   int Param1, Param2, Param3 ;
                   {
                   int LocIndx ;
                   char LocChar [14] ;
                   int LocWord ;
                   ...   ...

                                      - 2-33 -

             В языке  Си все подпрограммы являются также функциями;  любая
         подпрограмма может возвращать значение в вызывающую  программу. В
         связи с тем,  что приведенная функция не возвращает значение, она
         объявлена как пустая функция (void).
             Язык программирования Си обеспечивает использование автомати-
         ческих  (automatic)  переменных для запоминания локальных данных.
         Заметим, однако, что отсутствует стандарт, предписывающий порядок
         размещения в стеке локальных переменных.
             В листинге 2-8, рис. 2-1 и в приведенном выше фрагменте прог-
         раммы показано как язык Си помещает свои аргументы в порядке, об-
         ратном их объявлению.  Целью этого способа является то,  что если
         передается переменное количество параметров,  то вызываемая прог-
         рамма может всегда найти самый левый  параметр  на  фиксированной
         позиции  в  стеке.  Параметр  Param1  будет  всегда размещаться в
         [BP + 4],  независимо от того, какое количество параметров было в
         действительности передано. Программы на языке Си, допускающие ис-
         пользование этой особенности,  обычно используют самый левый  или
         первый параметр для передачи общего количества параметров,  пере-
         даваемых в вызываемую  программу.  При  этом  способе  вызываемая
         программа  может определить сколько параметров ей необходимо про-
         читать.
             Другой особенностью,  отмечаемой в языке Си, является то, что
         параметры почти всегда передаются по значению.  Если вызвать про-
         цедуру Example (пример) с переменной FOO,  то содержимое перемен-
         ной FOO будет помещено в стек.  Вызываемая программа, таким обра-
         зом,  работает с копией переданной переменной,  а не с ней самой.
         Исключение этого способа состоит в том, что массив обычно переда-
         ется  по адресу.  (В стандартном языке Си идентификатором массива
         является его адрес,  так что это кажущееся исключение в  действи-
         тельности  согласуется с синтаксисом языка Си).  Однако,  язык Си
         позволяет программисту передавать адрес  любой  переменной,  если
         потребуется.
            Компилятор языка Си фирмы "Майкрософт" поддерживает богатейшую
         среду программирования,  позволяющую опытному программисту полное
         управление памятью,  используемой модулем.  В приведенном примере
         представлена среда программирования языка Си по умолчанию, состо-
         ящая из вызова программы near (близкий) и ссылки  near на данные.
             Вопреки уже сделанным попыткам, не будем заменять версию про-
         цедуры Example (пример) программой на языке Си.  Одно из препятс-
         твий, которое необходимо преодолеть, состоит в согласовании имен,
         используемых между вызывающей программой на языке Си и вызываемой
         программой на языке Ассемблер.  Проблема заключается в  том,  что
         компилятор языка Си ставит префикс "подчеркивание" (_) перед все-
         ми именами.  Когда компилятор генерирует вызов программы  Example
         (пример),  он  реально предполагает,  что именем программы пункта
         назначения является "_Example".  Возможно,  что эта  терминология
         разработана для предотвращения коллизий между пространством имени
         компилятора и пространством имени Ассемблера. Если и вызывающая и
         вызываемая программы написаны на языке Си, то компилятор трансли-
         рует обе ссылки и поэтому нет никаких неприятностей.  Когда  одна
         из ссылок на языке Ассемблер,  то необходимо выполнить трансляцию
         самим. Эта трансляция применяется для выдачи имен переменных гло-
         бальных данных также успешно, как и программных меток.

                                      - 2-34 -
             Следует отметить два существенных момента:
             - в языке программирования Си имена ограничены 8 символами;
             - все имена в языке программирования Си чувствительны  к  ре-
         гистру.
             В языке программирования Си "Example"  и  "example"  это  два
         разных имени. Программы на языке программирования Ассемблер долж-
         ны ассемблироваться с переключателем /mx для предохранения от ис-
         пользуемого регистра любого имени.

                                                       Таблица 2-1
                 Соглашения о связях для языка Си фирмы "Майкрософт"
         _______________________________________________________________
                                   |
              Соглашение           |                 Описание
         __________________________|____________________________________
                                   |
          Ссылка на программу      | Near (близкие) или far (далекие)
          Ссылки на данные         | Near (близкие) или far (далекие)
          Стек очищается           | вызывающей программой
          Параметры передаются в   | обратном порядке
          Параметры передаются по  | значению
          Значения возвращаются в  | регистре AX или DX:AX
          Длина имени              | 8 символов
          Всем именам предшествует | символ "подчеркивание" (_)
         __________________________|____________________________________

             Последним требованием к языку  программирования  Си  является
         возможность  вызова  программ на языке Ассемблер.  Это требование
         обеспечивается тем,  что на языке Ассемблер функция  должна  быть
         объявлена  как public (общая),  а на языке Си - как extern (внеш-
         няя).  Краткие сведения о соглашениях о связях для языка програм-
         мирования Си фирмы "Майкрософт" приведены в таблице 2-1.

              Соглашения о связях для языка Паскаль фирмы "Майкрософт"

             Если листинг 2-8 приближается к синтаксису вызова  для  языка
         Си фирмы "Майкрософт", то соглашения о связях для компилятора язы-
         ка Паскаль фирмы "Майкрософт" лучше выражаются с  помощью примера,
         приведенного в листинге 2-4.  Вызов на Паскале эквивалентен вызову
         процедуры myproc (моя процедура),  закодированному следующим обра-
         зом:

               procedure MyProc (Param1, Param2, Param3 : integer) ;
               begin
                ...

             Главное отличие между языками Си и Паскаль состоит в том, что
         Паскаль  выполняет гораздо больше строгих проверок.  Эти проверки
         гарантируют:  что при вызове передается правильное  количество  и
         типы параметров;  что значения функции используются способом, со-
         ответствующим их типу и т.д.  Таким образом,  в отличие от  языка
         Си, в языке Паскаль программа должна быть объявлена либо процеду-
         рой (подпрограмма,  которая не возвращает значения), либо функци-
         ей.
             Язык Паскаль также обеспечивает  использование автоматических
         переменных для памяти локальных данных. Как и в языке Си, в языке

                                      - 2-35 -
         Паскаль нет стандартных решений о  назначении  порядка  локальных
         переменных в стеке. Также, как и в языке Си, в Паскале память для
         локальных переменных распределяется при вершине стека на  входе в
         вызываемую программу.  Если процедура MyProc использует локальные
         переменные LocIndx,  LocChar и LocWord,  то они должны будут ссы-
         латься   так,  как  показано в структуре StackFrame листинга 2-8.
         Паскаль эквивалентен программе, похожей на ниже приведенную:

               procedure MyProc (Param1, Param2, Param3 : integer) ;
               var
                   LocIndx, LocWord : integer ;
                   LocChar [1..14] : character ;
               begin
                ...

             Из листинга 2-4 можно увидеть,  что в отличие  от  языка  Си,
         язык  Паскаль  помещает  свои аргументы в порядке,  в котором они
         объявляются,  слева направо. Причина, по которой этот способ воз-
         можен,  состоит в том,  что компилятор языка Паскаль гарантирует,
         что все обращения, выдаваемые программой, обеспечивают правильное
         количество  и  типы аргументов.  Язык Паскаль просто не позволяет
         передавать переменное количество параметров,  поэтому порядок пе-
         редачи, используемый в языке Си, не требуется в языке Паскаль.
             Следствием строгой проверки вызова,  выполняемой языком  Пас-
         каль,  является то, что вызываемая программа всегда получает одно
         и то же количество аргументов,  позволяющее  вызываемой программе
         использовать  инструкцию RET N для очистки стека, нежели зависеть
         от вызывающей программы.
             Другим сходством  с  языком Си является то,  что язык Паскаль
         обычно передает свои переменные по значению,  однако, если требу-
         ется,  то переменные можно передавать по адресу, используя описа-
         ние var (переменная).

                                                         Таблица 2-2
                Соглашения о связях языка Паскаль фирмы "Майкрософт"
         ________________________________________________________________
                                       |
                  Соглашение           |           Описание
         ______________________________|_________________________________
                                       |
          Программные ссылки           | Far (далекие)
          Ссылки на данные             | Far (далекие)
          Стек очищается               | вызываемой программой ( RET N)
          Параметры передаются в       | порядке объявления
          Параметры передаются по      | значению
          Значения возвращаются в      | регистре AX или DX:AX
          Длина имени                  | 8 символов
          Все имена являются           | нечувствительными к регистру
         ______________________________|_________________________________

            В отличие от языка Си, компилятор языка Паскаль фирмы "Майкро-
         софт"  использует  модуль памяти LARGE (большой),  предполагающий
         вызовы far (далекий) и ссылки по памяти far (далекие).  Также,  в
         отличие  от Языка Си,  язык Паскаль распознает имена на любом ре-
         гистре,  несмотря на то,  что функция языка Ассемблер должна быть
         еще  объявлена как public (общая),  а ссылки языка Паскаль должны
         быть объявлены как extern (внешние).  Краткие сведения о соглаше-

                                      - 2-36 -
         ниях о связях в языке Паскаль фирмы "Майкрософт" приведены в  таб-
         лице 2-2.

           Соглашения о связях языков Фортран и Бэйсик фирмы "Майкрософт"

             Стандартные компиляторы  языков  программирования  Бэйсик   и
         Фортран  фирмы "Майкрософт" очень похожи на компилятор языка Пас-
         каль фирмы "Майкрософт". Таблица 2-3 справедливо показывает,  как
         много имеется сходства при стандартных вызовах. Однако, имеются и
         отличия.  Главным отличием от соглашений о связях  языка  Паскаль
         состоит в том,  что и Бэйсик и Фортран передают свои аргументы по
         ссылкам. Так как эти языки программирования передают адреса пере-
         менных,  любые  манипуляции  переменными,  выполняемые вызываемой
         программой, также изменяют значения переменных в вызывающей прог-
         рамме.
             В действительности, сходство между всеми четырьмя интерфейса-
         ми означает, что как легко написать подпрограмму на языке Ассемб-
         лер для Бэйсика и Фортрана,  также легко и для Паскаля или  языка
         Си.  Однако,  в  программах языков Бэйсик и Фортран для установки
         правильного интерфейса требуются большие усилия.
             Эквивалентом предложения  extern (внешний) в языке Бэйсик яв-
         ляется предложение DECLARE (объявить),  в то время  как  в  языке
         Фортран  требуется  предложение INTERFACE (интерфейс).  Каждое из
         этих предложений информирует соответствующий  компилятор  о  том,
         что  имя соответствующей программы должно находиться вне текущего
         модуля.  Для информирования компилятора о том, как сформатировать
         и  сгенерировать  правильный вызов,  могут потребоваться дополни-
         тельные параметры.  Соглашения о связях для языков Бэйсик и Форт-
         ран фирмы "Майкрософт" приведены в таблице 2-3.

                                                         Таблица 2-3
                  Соглашения о связях для языков Бэйсик и Фортран
                                 фирмы "Майкрософт"
          ___________________________________________________________
         |                      |                 |                  |
         |     Соглашение       |    Бэйсик       |   Фортран        |
         |______________________|_________________|__________________|
         |                      |                 |                  |
         |Ссылки на программы   |Far (далекий)    |Far (далекий)     |
         |Ссылки на данные      |Far (далекий)    |Far (далекий)     |
         |Стек очищается        |     вызываемой программой (RET N)  |
         |Параметры передаются в|         в порядке объявления       |
         |Параметры передаются  |по адресу Far(да-|по адресам Near   |
         |                      |лекий)           |(близкий)/Far (да-|
         |                      |                 |лекий)            |
         |Значения возвращаются |       регистре AX или DX:AX        |
         |Длина имени           |40 символов      |6 символов        |
         |Все имена представлены|на верхнем ре-   |нечувствительны   |
         |                      |гистре           |к регистру        |
         |______________________|_________________|__________________|

                         Модель сегмента фирмы "Майкрософт"

             Версия 5.0  макроассемблера  MASM фирмы "Майкрософт" дает воз-
         можность программисту быстро указывать надлежащие имена сегментов
         и  устанавливать  их для данного языка программирования с помощью
         директивы MODEL. Даже без версии 5.0 установка надлежащего шабло-

                                      - 2-37 -
         на языка Ассемблер относительно проста. Все четыре языка програм-
         мирования используют одинаковые первичные  имена  сегментов.  Имя
         кодового сегмента "_TEXT", а имя сегмента данных "_DATA". Для от-
         дельных интерфейсов могут потребоваться  дополнительные сегменты,
         и  многие  модели  компиляторов фирмы "Майкрософт" требуют, чтобы
         стек помещался бы в сегмент данных.  Однако, простой программе на
         языке Ассемблер не нужно беспокоиться об этом,  так как она может
         просто отключить функционирование стека в вызывающей  программе и
         все необходимые установки будет поддерживать главная подпрограмма
         языка высокого уровня.

                  2  0Назначение и использование локального ЗУ в памяти

             Имеется три способа распределения памяти для переменных.  Бу-
         дем рассматривать локальное ЗУ (локальную память)  в  оперативной
         памяти и локальную память в стеке. Сейчас рассмотрим локальное ЗУ
         в распределяемой памяти.  Распределяемая память должна получаться
         из неиспользуемой памяти системы (часто называемой пулом памяти).
         MS-DOS поддерживает функции,  которые могут быть использованы для
         распределения,  перераспределения и установки размера блоков сис-
         темной памяти.  После распределения памяти программист может реа-
         лизовать  свою  личную (персональную) схему управления памяти для
         управления памятью в узком участке.  Однако сейчас сконцентрируем
         свое  внимание  на  возможностях MS-DOS,  начиная с функции 48h -
         "распределить память".
             После получения блока памяти,  программа должна иметь возмож-
         ность его адресации.  Память, распределяемая MS-DOS, предоставля-
         ется  в  "кусках" по 16 байтов,  называемых "paragraph" (парагра-
         фом). MS-DOS возвращает указатель на эту память, которая содержит
         16-битовый адрес памяти блока. Сегменты адресуются как параграфы,
         при этом указатель должен быть загружен в один из регистров  сег-
         мента (но не в регистр CS!). Обычно для повторного доступа к бло-
         ку памяти используется либо сегмент данных, либо внешний сегмент.
         Если подпрограмма, которая распределяла память, не является глав-
         ной подпрограммой программы, то старое значение регистра сегмента
         должно  сохраняться и восстанавливаться перед выходом из подпрог-
         раммы.  Кроме того,  перед выходом из подпрограммы распределенная
         память  должна быть возвращена в систему.  Для возврата распреде-
         ленной памяти блока в систему используется функция 49h  MS-DOS  -
         "освободить распределенную память". В листинге 2-11 показано, как
         подпрограмма исполняемой программы .EXE будет  распределять,  ис-
         пользовать  и освобождать память,  используемую как локальную па-
         мять (локальное ЗУ).


          Листинг 2-11. Распределение локальной памяти посредством MS-DOS
         -----------------------------------------------------------------

                     common   SEGMENT ; общие данные, используемые всеми
                     com_1   dw     ?
                     com_2   db     14 DUP (?)
                     common    ENDS
                     dummy_dat  STRUC      ; описание структуры,
                     dummy_1 dw     ?      ; используемой с
                     dummy_2 db     14 DUP (?) ; распределенной памятью
                     dummy_dat ENDS
                             ASSUME ds:common ; доступ к данным COMMON

                                      - 2-38 -
                     local_example  PROC    NEAR ; процедура example
                     push    ds        ; сохранение предыдущего DS
         B8 ---- R   mov     ax,common ; COMMON настраиваемая MS-DOS
                     mov     ds,ax
                     push    es        ; сохранение предыдущего ES
                     mov     ah,048h   ; распределение памяти
                     mov     bx,1      ; запрос 1 блока (16 байт)
                     int     21h       ; вызов MS-DOS
                     jc      not_alloc ; перенос означает сбой распред-я
                     mov     es,ax     ; если распределена, то ее адрес
         ;
         ;                 три примера адресации
         ;
         A1 0000 R   mov     ax,com_1 ; надлежащий сег.-предполагается DS
         B8 0000     mov     ax,dummy_1 ; ошибочный сег.-непосредственный
         26: A1 0000 mov     ax,es:dummy_1 ; надлежащий сег.-замещаемый
                     ;
                     mov     ah,049h   ; освобождение распредел. памяти
                     int     21h       ; вызов MS-DOS
                     jnc     free_ok   ; нет переноса т.е. хорошо
                     not_alloc:
                     ; Сообщение об ошибке, если сбой, распр-я или удал-я
                     free_ok:
                     pop     es        ; восстановление ES
                     pop     ds        ; восстановление DS
                     ret
                     local_example   ENDP  ; конец примера
         -----------------------------------------------------------------

             Листинг 2-11 содержит оба вызова функций MS-DOS "Распределить
         память" и "Освободить память". Вместо регистра DS для указания на
         только что распределенную память,  был использован регистр ES,  а
         регистр  DS зарезервирован для доступа к области общих переменных
         программы.  Заметим, что в отличие от примера стека, для выполне-
         ния доступа к используемой структуре,  определенной здесь, требу-
         ется оператор замещения сегмента (:). Без замещения сегмента инс-
         трукция  mov  ax,dummy_1 не выполняет генерацию ссылки на память,
         используя регистр ES,  но взамен этого генерирует загрузку смеще-
         ния (в нашем случае нуль) в регистр AX.  При добавлении замещения
         сегмента к инструкции mov ax,es:dummy_1 макроассемблер MASM гене-
         рирует  передачу  памяти  из смещения dummy_1 во внешний сегмент.
         Замещение сегмента в листинге 2-11  показано  с  байтом  префикса
         26:.
             При использовании в  программе  нескольких  сегментов  данных
         программист несет ответственность за управление используемыми об-
         ластями данных. Например, если программа X распределяет локальную
         память  и  обновляет  регистр  DS для доступа к этой области,  то
         программист должен помнить о том, что эта область данных по умол-
         чанию  принимается областью данных для всех программ,  вызываемых
         программой X.  Общая область данных,  которая была  определена  в
         программе, доступна еще путем загрузки либо регистра DS, либо ре-
         гистра ES из переменной сегмента,  как показано в  листинге  2-6.
         Программы, изменяющие содержимое своих регистров сегмента, должны
         сохранять и  восстанавливать  первоначальные  значения  регистров
         сегмента для предотвращения своих порождаемых задач от ошибок.
             Всякий раз,  когда в программе используется более одного сег-
         мента данных или внешних сегментов, программист должен быть очень

                                      - 2-39 -
         внимателен к директивам ASSUME (присвоить),  используемым в прог-
         рамме.  При ассемблировании обычных ссылок на память макроассемб-
         лер MASM сначала ищет их таблицу внешних символов для имен  пере-
         менных,   к  которым  осуществляется  доступ.  Если  MASM  найдет
         переменную в таблице символов,  он пытается создать  ссылку,  ис-
         пользуя сегмент,  в котором определена эта переменная.  Если этот
         сегмент отсутствует (т.е. отсутствует соответствующее предложение
         ASSUME), то MASM генерирует сообщение об ошибке "Can't reach with
         segment reg" (нельзя найти регистр сегмента).
             Если MASM не может найти переменную в таблице символов, то он
         предполагает,  что она находится в сегменте данных.  Если  и  это
         предположение  окажется  неудачным,  то  MASM  пытается исправить
         ошибку во время второй передачи путем присоединения  к инструкции
         префикса замещения сегмента.  При неудаче,  получение этого байта
         вызывает другое сообщение об ошибке "Phase error  between passes"
         (ошибка фазы между передачами).
             В случае неудачи или ссылки "вперед", т.е. когда имя перемен-
         ной  еще не находится в таблице символов,  программист должен ис-
         пользовать оператор замещения сегмента (:) для более чистого  оп-
         ределения   макроассемблером  MASM  используемого  сегмента.  Для
         управления доступом в программе также полезен оператор  SEG. Этот
         оператор позволяет программисту получать значение сегмента (базо-
         вый адрес сегмента) для любой  определенной  переменной.  Ссылки,
         создаваемые с помощью предложения SEG, настраиваются MS-DOS и по-
         лезны для создания настраиваемых ссылок вместо абсолютных ссылок.

                       Введение в управление памятью в MS-DOS

             Пример, приведенный в листинге  2-11,  зависит  от  имеющейся
         свободной памяти внутри системы. По умолчанию MS-DOS вырабатывает
         распределение всей памяти для себя при загрузке.  Вызов распреде-
         ления памяти будет неудачным,  т.к. процесс уже имеет всю память,
         даже если он не знает про это. Если программа желает использовать
         функцию распределения памяти,  то некоторая память, полученная во
         время загрузки,  должна быть возвращена в систему. Обычно процесс
         будет  стремиться вернуть всю память ,  которую он не занял прог-
         раммным кодом или буферами.
             Функция, обеспечиваемая  MS-DOS  для обработки возврата части
         распределенной памяти программы в систему,  является функцией 4Ah
         -  "Модификация блока распределенной памяти".  Она позволяет про-
         цессу "вырезать" память из его блока распределения памяти, приня-
         того по умолчанию.
             Заметим, что имеются способы предотвращения процесса от расп-
         ределения всей памяти при загрузке, но их рассмотрение отложим до
         главы 3,  где тема загрузки программ и программных файлов  MS-DOS
         будет обсуждаться более подробно.
             Параметры, требующиеся для функции "Модифицировать блок расп-
         ределения памяти",  это адрес сегмента блока,  который необходимо
         модифицировать, и новый размер блока. Адрес сегмента блока, кото-
         рый содержит программу (размер которой нужно модифицировать), пе-
         редается через PSP (Program Segment Prefix - сегмент программного
         префикса).  PSP является разделом памяти, которым начинается каж-
         дая программа MS-DOS. Подробно содержимое PSP описывается в главе
         3.  Сейчас же для нас важно только то, что адрес сегмента PSP яв-
         ляется адресом сегмента блока, который необходимо модифицировать,
         и нам необходим этот адрес.
             Что касается определения этого параметра, то оно различно для

                                      - 2-40 -
         файлов  типа .COM и файлов типа .EXE. Рис. 2-3 показывает располо-
         жение  памяти для файлов типа .COM и типа .EXE.  PSP является пер-
         вым элементом для каждого типа файлов.  В программе типа .COM  PSP
         содержится в первых 256 байтах программного сегмента и адрес прог-
         раммного сегмента (во всех регистрах  сегмента)  является  адресом
         сегмента PSP.
             Для файлов типа .EXE PSP располагается в его собственном  сег-
         менте. Однако, всякий раз при загрузке программы типа .EXE и полу-
         чении управления от MS-DOS,  оба регистра DS и ES  содержат  адрес
         сегмента PSP.  Таким образом,  для программы любого типа адрес PSP
         можно будет получить, по крайней мере, из регистра DS или регистра
         ES.  Кроме  того,  пользователи MS-DOS версии 3.0 (или выше) могут
         использовать программу получения адреса сегмента программного пре-
         фикса (PSP) - функцию 62h.  MS-DOS возвращает значение адреса сег-
         мента программного префикса в регистре BX.
             Т.к. функция "Модифицировать блок распределенной памяти" пред-
         полагает адрес блока в регистре ES,  то функция может быть вызвана
         непосредственно  при  выполнении запуска программы,  поскольку ре-
         гистр ES уже имеет адрес PSP.

                |\/\/\/\/\/|                         |\/\/\/\/\/|
                |  Память  |                         |  Память  |
                |файла типа|                         |файла типа|
                |   .COM   |                         |   .EXE   |
           0000 |----------|           PSP           |----------|
                |    PSP   |<----------------------->|    PSP   |
           0100 |----------|                         |----------|
                |   Коды   |                         | Сегмент A|
                |    или   |                         |----------|
                |  данные  |                         | Сегмент B|
                |----------|     Конец программы     |----------|
                |   Стек   |<----------------------->| Сегмент C|
           FFFE |----------|                         |----------|
            или | Неисполь-|                         | Неисполь-|
          более |  зуемая  |                         |  зуемая  |
         высокая|  память  |   более высокая память  |  память  |
          память ---------- <-----------------------> ----------

                  Рис.2-3. План памяти программы MS-DOS и сегмента
                           программного префикса

             После того, как найден адрес блока памяти, необходимо опреде-
         лить общее количество памяти,  необходимое для сохранения. Разли-
         чия между программами .COM и программами  .EXE  здесь  становятся
         уже  более заметными.  Для программы типа .EXE размер должен быть
         определен путем вычитания адреса начала сегмента  PSP  из  адреса
         сегмента  фиктивного сегмента,  расположенного в конце программы,
         как показано в листинге 2-12.  Почему используются адреса сегмен-
         тов?  Функция  4Ah ожидает размер в параграфах,  а адрес сегмента
         обычно является адресом параграфа.

          Листинг 2-12. Функция 4Ah - "Модифицировать блок распределенной
                  памяти" - изменение размера программы типа .EXE
         -----------------------------------------------------------------

         resize PROC NEAR
                mov     ax,es           ; получение адреса PSP

                                      - 2-41 -
                mov     bx,SEG end_addr ; получение адреса сл. сегмента
                sub     bx,ax           ; разность - размер программы
                mov     ah,04Ah         ; модификация распредел-й памяти
                int     21h             ; вызов MS-DOS
                jnc     short resize_ok ; нет переноса => хорошо
                mov     ax,04C00h       ; перенос => сбой--авар. заверш.
                int     21h
         resize_ok:
                ret
         resize ENDP
         ;
         ; Оставшаяся  часть программного кода в этой программе END_ADDR
         ; является последним  элементом  перед  предложением  END.  Это
         ; сделано для того,  чтобы предложение END_ADDR связалось бы как
         ; последний сегмент, если используется более одного исходного
         ; файла.
         ;
          end_addr  SEGMENT
          end_addr  ENDS
                  END
         -----------------------------------------------------------------

             Для программы типа .COM требуется меньше хлопот. В отличие от
         программы типа .EXE,  которая имеет определенный размер, установ-
         ленный  компоновщиком  LINK,  программы  типа .COM могут изменять
         свой размер.  Размещение стека в программе типа .COM, которое ус-
         танавливается MS-DOS,  может изменяться от конца сегмента (FFFEh)
         на 256 байтов больше  программы  (минимальный  размер,  требуемый
         MSDOS для стека).  Пользователь может выбирать первое,  принимая,
         что MS-DOS обеспечила и изменила размер стека (установленный раз-
         мер 64 кбайт (1000h) параграфов) или все,  что осталось; или вто-
         рое, пересылая стек и изменяя размер, основываясь на этом. Второй
         выбор освобождает больше памяти и,  таким образом, является более
         предпочтительным и рекомендуемым  фирмами "Майкрософт"  и  "ИБМ".
         Листинг 2-13 содержит пример программы .COM, который устанавлива-
         ет свой собственный стек и изменяет размер своего начального бло-
         ка распределения на более умеренный размер.

          Листинг 2-13. Функция 4Ah - "Модифицировать блок распределенной
                  памяти" - изменение размера программы типа .COM
         -----------------------------------------------------------------

         code_seg  SEGMENT
                   ASSUME  cs:code_seg
                   ORG     0000h
         seg_org   EQU     $
                   ORG     0100h
         main      PROC    FAR
         start:
                   mov     sp,offset stack
                   call    resize
         ;
         ; здесь может выполняться оставшаяся часть программы
         ;
         main      ENDP
         resize    PROC    NEAR
                   mov     bx,(offset last_byte - seg_org + 15) shr 4

                                      - 2-42 -
                   mov     ah,04Ah        ; модификация распр-й памяти
                   int     21h            ; вызов MS-DOS
                   jnc     short resize_ok ; нет переноса => хорошо
                   mov     ax,04C00h      ; перенос => сбой--ав.заверш.
                   int     21h
         resize_ok:
                   ret
         resize    ENDP
                   db      32 DUP ('stack   ')
         stack:
         last_byte EQU     $
         code_seg  ENDS
                   END     start
         -----------------------------------------------------------------

             Интересной частью  этой программы является способ определения
         размера результирующей программы.  Для преобразования  количества
         байтов программы в количество параграфов  по-существу посредством
         деления на 16,  используется оператор SHR  макроассемблера  MASM.
         Что не является таким очевидным так это то,  почему seg_org вычи-
         тается из смещения last_byte.  Оператор SHR не выполняется, когда
         применяется  для смещения,  и он вырабатывает сообщение об ошибке
         "Constant was expected" (ожидалась константа).  Однако,  разность
         между двумя смещениями считается постоянной, делая выражение при-
         емлемым для макроассемблера MASM.  Заметим,  что  seg_org  должна
         иметь нулевое смещение так,  чтобы размер был относительно начала
         сегмента. Если бы использовалась метка start, то были бы потеряны
         последние 100 (шестнадцатиричное значение) байт программы. (Заме-
         тим,  что last_byte:  для вычислений работает также хорошо, как и
         last_byte equ $).
             Кроме того,  для успешного  использования освобождения памяти
         прием  вычитания двух смещений (либо метки,  либо количества) для
         получения константы,  может быть успешно использован для всех ти-
         пов  операций,  где  размер требуется в выражениях,  использующих
         константы.  Рассмотрение этого применения для задач  выравнивания
         буферы данных на границу параграфа будет приведено в главе 6.

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

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




                Защита данных и управление областью действия данных

             Технические приемы, используемые при повторно  входимом коди-
         ровании, подвели нас к другому аспекту модульного  программирова-
         ния:  защите данных в программе от случайного  изменения.  Разру-
         шение  ценных  данных  наиболее  часто  происходит  тогда, когда
         одна часть программы ошибочно изменяет данные, относящиеся к дру-

                                      - 2-43 -
         гой части программы. Путем следования некоторым основным правилам
         возможность такого события может быть значительно уменьшена.  Са-
         мое главное правило состоит в том,  что модульность данных  прог-
         раммы также хороша как и модульность программного кода, управляю-
         щая рядом данных,  к которым подпрограмма может иметь доступ. Это
         понятие очень часто называется "областью действия данных".  Обра-
         тимся к тому,  что мы уже изучили, и посмотрим как это можно при-
         менить для наших новых проблем.

                 Локальная память в сравнении с глобальной памятью

             Память человека в любое конкретное  время  может  иметь  дело
         только с ограниченным количеством понятий.  Для программистов это
         подразумевает то,  что с ростом количества манипулируемых и запо-
         минаемых элементов растет и количество сделанных ошибок.  При ис-
         пользовании локальной памяти для подпрограмм программист уменьша-
         ет  количество  элементов данных,  которое должно быть запомнено.
         Чем иметь дело с областями данных,  содержащими сотни переменных,
         программист может теперь иметь дело с областями данных,  содержа-
         щими небольшое количество данных.  Может  существовать  небольшое
         количество "узких" областей данных,  каждая из которых может быть
         проверена программой,  использующей ее, потому что каждая область
         безопасна только тогда,  когда есть уверенность, что другие прог-
         раммы не связаны с ней.  Любой из представленных способов для пов-
         торно-входимых программ служит для распределения временной локаль-
         ной памяти данных.
             Глобальные области данных, также известные как общие области,
         могут быть разбиты на модули. В этом случае вместо одной монолит-
         ной  области данных создается некоторое количество узких областей
         данных. После этого подпрограммы будут иметь дело только с такими
         участками глобальных данных, которые требуются для обработки. Это
         накладывает на часть программистов требование  быть внимательными
         и осторожными с директивами ASSUME в содержимом регистров сегмен-
         та,  но такая явная обработка общих данных к тому же делается чи-
         ще,  чем  доступ и затем изменение критических данных.  Например,
         общая область данных, содержащая строки текста и символьные конс-
         танты,  не нуждается в части подпрограмм вычисления чисел,  также
         как таблицы значений синуса и косинуса не нужны подпрограмме вво-
         да информации с терминала.
             В стек должно передаваться столько параметров,  сколько необ-
         ходимо  для  уменьшения количества внутренних обращений к данным.
         Всякий раз, когда несколько программ должны иметь доступ к облас-
         тям данных с целью передачи параметров, вероятность ошибки увели-
         чивается.
             Общие данные обычно должны быть определены с помощью директи-
         вы DEFINE DATA (определить данные) так,  чтобы содержимое области
         оставалось  без изменения и не было субъектом случайного удаления
         при ошибочном освобождении программой с помощью функции  "Освобо-
         дить распределенную память".

                          Использование регистров сегмента

             Регистры сегмента  позволяют программисту ограничивать диапа-
         зоны ссылок на данные. Путем изменения базы сегмента, содержащего
         данные,  архитектура  машины  автоматически вынуждает программу к
         "окну" доступа в 64 Кбайт.  Если более восприимчивые данные поме-
         щаются  в нижние  области памяти, то после того, как регистр сег-

                                      - 2-44 -
         мента изменится для указания на  высший  адресуемый  блок  памяти,
         данные в нижней области памяти будут полностью защищены от несанк-
         ционированного доступа.


                        Управление размером доступных данных

             Программист в дальнейшем может управлять размером окна данных
         путем установки bounds-checking (проверка границ)  в  массиве,  к
         которому осуществляется доступ.  Одна из наиболее типичных ошибок
         в данных происходит тогда, когда доступ к массиву выполняется че-
         рез его границы.  Все, что происходит на границе массива, теряет-
         ся. Проверка границы массива может быть выполнена с помощью прос-
         того  макроса,  как показано в листинге 2-14.  Для программистов,
         работающих с процессором 80x86, для выполнения этой проверки пре-
         дусмотрена инструкция BOUND (граница).  Для обеспечения совмести-
         мости с инструкцией BOUND был написан макрос bound,  показанный в
         листинге 2-14.

                    Листинг 2-14. Макрос проверки границ массива
         -----------------------------------------------------------------

         ; Сравнение границ массива, содержащихся в общем регистре REG,
         ; с двумя последовательными значениями, размещенными в памяти
         ; по адресу MEM32. Это есть сравнение целых со знаком.
         bound   MACRO   reg,mem32
                 LOCAL   out_bound,in_bound
                 pushf                          ; сохранение флажков
                 cmp     reg,word ptr mem32     ; проверка нижнего предела
                 jl      out_bound              ; превышение индекса
                 cmp     reg,word ptr mem32 + 2 ; проверка верхн. предела
                 jle     in_bound               ; индекс хороший
         out_bound:
                 popf                           ; очистка стека
                 INT     5                      ; принимаемое действие
         in_bound:
                 popf                           ; восстановление флажков
                 ENDM
         -----------------------------------------------------------------

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

                             Защита целостности данных

             Другой областью, восприимчивой к разрушению, является стек. В
         связи  с  тем,  что стек смешивает коды и данные,  ошибка в стеке
         непременно явится результатом ошибки в программе при попытке про-
         цессора использовать данные как ссылку на инструкцию.
             Два наиболее  общих случая разрушения стека вызывают проблемы
         ошибочного   выравнивания.  Первый  случай - из-за несоответствия
         операций PUSH и POP,  а второй - благодаря попытке извлечь данные

                                      - 2-45 -
         с помощью инструкции POP, которые были помещены по другую сторону
         инструкций CALL или RET. Эти проблемы можно избежать только путем
         удаления особого внимания "спариванию" инструкций PUSH и POP, ис-
         пользуемых в программе,  и обеспечения гарантии в том,  что такие
         пары не выполняют ссылки за границы подпрограммы.
             В случае передачи параметров возникает вопрос, а как подпрог-
         рамма выполняет очистку стека?  Обычно, правило для такого случая
         состоит  в том,  что программа,  передавшая в стек данные по инс-
         трукции PUSH,  получает данные из стека по инструкции  POP.  Если
         следовать  этому  правилу,  то путем чтения листинга одной,  а не
         двух подпрограмм программист может проверить,  что стек выровнен.
         Однако,  жесткое следование этому правилу предотвращает использо-
         вание для очистки стека инструкций микропроцессора  8086  RET  N.
         Если  интерфейс между двумя программами полностью отлажен и наде-
         жен, то приемлемый риск состоит в использовании инструкции RET N.
             Всякий раз, когда программа должна быть закодирована для при-
         ема переменного количества параметров,  не  следует  использовать
         инструкцию  RET N.  Имеются различные пути ограничения глобальной
         возможности очистки стека только  для  установленного  количества
         переменных,  но все,  вызываемые ими трюковые манипуляции со сте-
         ком,  являются затруднительными для понимания и более трудными  в
         отладке. Если подпрограмма должна принимать переменное количество
         параметров,  то очищать эти параметры из стека должна  вызывающая
         подпрограмма.  Кроме  того,  вызывающая  подпрограмма должна ясно
         указывать вызываемой программе количество  передаваемых  для  нее
         параметров.
             Все операции, выполняемые над стеком, за исключением операций
         PUSH  и POP,  должны происходить под флагом указателя стека и для
         доступа к стеку использовать регистр BP.  Это значит,  что указа-
         тель стека должен быть установлен в значение ниже манипулируемого
         элемента.  Если манипулируемые данные  остаются  неизменными,  то
         возникает  прерывание.  По этой же причине нельзя непосредственно
         манипулировать указателем стека до тех пор,  пока  не  произойдет
         переключение  стека,  или открытие памяти стека.  Если произойдет
         прерывание в то время,  когда указатель стека не указывает истин-
         ную  вершину  стека,  то данные в стеке могут быть потеряны.  Все
         сказанное выше не является предостережением для умелого использо-
         вания манипуляции стеком.

                                     Заключение

             В зтой главе было рассмотрено множество тем: от теоретической
         природы  структурного  программирования  до  подробного изложения
         макроассемблера MASM, MS-DOS и функционирования семейства микроп-
         роцессоров 8086. Была предпринята попытка получить некоторые аль-
         тернативные подходы в структурном программировании для нужд поль-
         зователя  ПЭВМ.  Хотя маловероятно,  что все или даже большинство
         этих технических приемов появится в Ваших небольших программах на
         языке  Ассемблер,  мы  будем благодарны за то,  что многие из них
         найдут применение в Ваших обширных проектах.  И,  если вспомнится
         только один момент,  то все равно: сначала все обдумайте, а коди-
         руйте только после обдумывания.
             Большинство более практических указаний о MASM и MS-DOS вновь
         "всплывут на поверхность" при обсуждении других вопросов в после-
         дующих  главах данного руководства.  Попытайтесь вывести примеры,
         приведенные в этой главе,  и Вы получите удовольствие от  их  ис-
         пользования.  Из приведенного выше  Вам понадобится многое. Боль-

                                      - 2-46 -
         шая  часть  отдельных  вводных  сведений  по  управлению  памятью
         MS-DOS,   приведенных в этой главе, послужит фундаментом для гла-
         вы 3 "Управление программами и памятью".

                 Глава 3. УПРАВЛЕНИЕ ПРОГРАММАМИ И ПАМЯТЬЮ

                 Память MS-DOS
                 Процессы MS-DOS
                 Резидентные программы
                 Функция 4Bh - загрузка и выполнение программ
                 Переключение контекста и переключение стека
                 Введение в резидентную часть оперативной памяти
                 REMOVE - пример интегрированной программы
                 Заключение

             В  предыдущей  главе  были изучены средства создания программ
         MS-DOS и  различные  пути их структурирования.  Теперь рассмотрим
         как программы MS-DOS существуют в среде MS-DOS.  При этом мы  для
         более полного пояснения некоторых тем,  ссылающихся на предыдущие
         главы, неоднократно будем возвращаться назад: префикс программно-
         го сегмента,  работа по распределению памяти MS-DOS,  и механизм,
         используемый для загрузки программ MS-DOS. В заключение мы введем
         механизм для установки программ,  остающихся резидентными - тема,
         развиваемая в главе 4 и обсуждающая завершение и оставление в па-
         мяти  резидентных  программ  (TSR  -  terminate and stay resident
         programs).

                                   Память MS-DOS

             Самый простой способ понимания среды  функционирования MS-DOS
         состоит в рассмотрении формата памяти MS-DOS, моделей распределе-
         ния своей ограниченной памяти для всех выполняемых ею и  конкури-
         рующих между собой целей.  Несмотря на отсутствие общих предписа-
         ний MS-DOS по отдельным форматам  памяти,  огромная  популярность
         стандартов фирмы "ИБМ" и их последовательное принятие обеспечива-
         ют нас форматом памяти де-факто.

                          Формат физической памяти MS-DOS

             MS-DOS была разработана для устройств центрального процессора
         (CPU) 8086/8088,  позволяющих адресовать суммарную память объемом
         1 Мбайт. Типичное использование и размещение этой памяти показано
         на Рис.3-1. Первые 10 сегментов ("кусков" по 64 кбайт) этой памя-
         ти относятся к области пользователя.  Это область,  объемом в 640
         Кбайт,  в  которой расположены сама MS-DOS и прикладные программы
         пользователя.  Оставшиеся  6  сегментов,  составляющие  в   сумме
         384 Кбайт, называются  системной областью и резервируются для ис-
         пользования ROM-BIOS (Постоянное запоминающее  устройство  (ПЗУ)-
         базовая  система  ввода-вывода)  и  для связи с другими платами в
         системе.  Заметим, что Рис.3-1 значительно упрощает использование
         системной  области. В действительности, имеется много типов плат,
         которые используют эту область для  многих  целей,  однако, будем
         рассматривать только общую схему.

                         Расширяемая и расширенная память

             С появлением MS-DOS были разработаны более мощные центральные
         процессоры. Центральные процессоры 80286 и 80386, каждый из кото-
         рых имеет расширяемые пределы адресуемой памяти,позволяют в одной
         системе размещать мегабайты памяти. Какая польза от того, что MS-
         DOS  получит сколько-нибудь байтов из этой дополнительной памяти?
                           
                                      - 3-2 -
                    Адрес     Использование памяти
                    FFFFF .------------------------------------------
                          |      Системное ПЗУ      |     ^       ^
                    F0000 |-------------------------|     |       |
                          |  Используется системой  |     |       |
                    E0000 |-------------------------| 384 кбайт   |
                          |  Используется системой  |ПЗУ или др.  |
                    D0000 |-------------------------|     |       |
                          | Видеопамять (Video RAM) |     |       |
                    C0000 |-------------------------|     |       |
                          |       Графика EGA       |     V       |
                    A0000 |-------------------------|-------      |
                          |       Пользователь      |     ^       |
                    90000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    80000 |-------------------------|     |    1 мбайт
                          |       Пользователь      |     |       |
                          |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    60000 |-------------------------| 640 кбайт   |
                          |       Пользователь      |  Область    |
                    50000 |-------------------------|пользователя |
                          |       Пользователь      |     |       |
                    40000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    30000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    20000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    10000 |-------------------------|     |       |
                          |  Используется системой  |     V       V
                    00000  ------------------------------------------
            Рис.3-1. Стандартный формат памяти IBM PC/XT/AT для MS-DOS

             Непосредственно никакая,  но в большинстве случаев эта допол-
         нительная  "расширенная  память" (поскольку она простирается выше
         границы в 1 Мбайт) может часто быть использована  как  псевдодиск
         или в более общем плане, как другой тип дополнительной памяти MS-
         DOS,  называемой "расширяемой памятью" (потому, что она "расширя-
         ет" основной предел MS-DOS в 640 Кбайт).
             Для MS-DOS версии 3.3 и более ранних версий продукты расширя-
         емой памяти доступны в трех разновидностях.  Первая  спецификация
         расширяемой  памяти  была  разработана совместно фирмами "Лотус",
         "Интел" и "Майкрософт" и называлась LIM  EMS  версии  3.2  (Limit
         Expanded Memory Specification - предельная спецификация расширяе-
         мой памяти).  Несколько позже  фирмы  "Аштон-Тэйт",  "Квэдрим"  и
         "АСТ"  разработали  улучшенный  стандарт  AQA  EEMS (the Enhanced
         Expanded Memory Specification - улучшенную спецификацию расширяе-
         мой памяти). Фирмы "Лотус", "Интел" и "Майкрософт" соединили луч-
         шие стороны AQA EEMS в LIM EMS версии 4.0. Все системы EMS состо-
         ят  из  памяти  (на соединительной  плате или плате расширения) и
         администратора  улучшенной  памяти  EMM  (the   Enhanced   Memory
         Manager)  -  устанавливаемого драйвера устройства.  Для установки
         функций EMS резервируется прерывание MS-DOS с номером 67h. MS-DOS
         версии 4.0 и выше,  как часть операционной системы,  поддерживает
         стандарт LIM  EMS  версии  4.0.  Реализация аппаратных средств от
         производителя к производителю меняется.  Программное  обеспечение
         EMS  MS-DOS  версии 4.0 состоит из устанавливаемого драйвера уст-
         ройства,  а, фактически, любого драйвера устройства EMS и совмес-
         тимого соединения аппаратных средств, которые могут быть заменены

                                      - 3-3 -
         применяемой операционной системой.
             Расширяемая память  является  результатом  появления  в среде
         MS-DOS устойчивых традиций использования  страничной  памяти  или
         памяти коммутации банков.  При  этом подходе большой раздел памя-
         ти, который лежит вне адресного пространства процессора, "отобра-
         жается" малыми областями на многие маленькие разделы памяти,  ле-
         жащие внутри адресного пространства процессора.  В то  время  как
         процессор  не  может  адресовать большой раздел памяти непосредс-
         твенно, он может выбрать или дойти до любой конкретной части, по-
         добно выбору страницы в книге.
             В спецификации расширяемой памяти MS-DOS или EMS большая  фи-
         зическая  память  отображается  в  16-килобайтные  разделы памяти
         MS-DOS, называемые страницами. Соответствующее 16-килобайтное ад-
         ресное  пространство  в памяти MS-DOS называется страничным фрей-
         мом. Количество поддерживаемых страничных фреймов и размещение их
         внутри  системы MS-DOS изменяется в зависимости от типа платы ис-
         пользуемой расширяемой памяти, и существующей конфигурации систе-
         мы.
             Глава 7 посвящена обзору памяти EMS,  описывающей методы дос-
         тупа,  стандарт EMS и пр.  В данном обсуждении мы хотя и признаем
         существование памяти EMS,  но не уделяем  ей  большого  внимания.
         Прежде  всего нас будет интересовать,  как сама MS-DOS использует
         память, и для нас достаточно отметить, что память EMS должна быть
         отображена  на стандартное адресное пространство памяти для того,
         чтобы быть доступной для MS-DOS.  (Имеются соображения о том, что
         последующие  версии  MS-DOS  могут использовать память EMS непос-
         редственно, реально преодолевая границу в 640 Кбайт).

                        Использование памяти MS-DOS

             К этому моменту мы установили, что по текущему стандарту фак-
         тически  MS-DOS имеет 640 Кбайт памяти для использования ею самой
         и прикладными программами пользователя.  В типовой системе MS-DOS
         эта  память будет распределяться так,  как показано на Рис.  3-2.
         Рассматривая Рис.3-2, можно заметить, что большинство приведенных
         адресов указано приблизительно и зависит от версии MS-DOS,  физи-
         ческой конфигурации системы и опций, указываемых пользователями в
         файлах конфигурации системы CONFIG.SYS и AUTOEXEC.BAT.  Кроме то-
         го, размеры сегментов, указанные на Рис. 3-2, показаны не в масш-
         табе,  а в соответствии с данной относительной позицией различных
         компонентов.
             На Рис.3-2  есть несколько областей,  которые требуют поясне-
         ния.  Заметим, что первая область COMMAND.COM появляется на схеме
         памяти  дважды.  Загружены  две  копии COMMAND.COM?  Нет,  просто
         COMMAND.COM загружается в две отдельные области.  Область, разме-
         щенная выше драйверов устройств, сохраняется в памяти постоянно и
         называется резидентной частью. Эта часть отвечает за корректность
         завершения  программ обработки и любые ошибки программы пользова-
         теля, которые возникают в результате завершения работы программы.
         Этот  раздел  является "порождающей программой" любой выполняемой
         программы пользователя.  Другой раздел COMMAND.COM, размещенный в
         верхней части схемы памяти, является частью, которая обеспечивает
         интерфейс пользователя с MS-DOS. Эта часть называется нерезидент-
         ной,  потому что она представлена только тогда, когда не выполня-
         ются программы пользователя, или когда программа пользователя пы-
         тается загрузить другую программу.


                                      - 3-4 -
                     Адрес    Использование памяти
                     А0000 .-----------------------------------------
         или вершина облас-|      COMMAND.COM      |      ^      ^
          ти пользователя  |-----------------------|      |      |
                           |                       |      |      |
                           | Нерезидентная область |Используется |
                           |       программы       | программами |
                           |                       |      |      |
                           |-----------------------|      |      |
                           | Резидентные программы |      V      |
                           |---------------------------------    |
                           |      COMMAND.COM      |          Область
                           |-----------------------|       пользователя
                           |   Драйверы устройств  |        (максимум
                           |-----------------------|         640 кбайт)
                           |      Буферы MS-DOS    |             |
             10000 - 14000 |-----------------------|             |
                           |                       |             |
                           |      Ядро MS-DOS      |             |
                           |                       |             |
             08000 - 0A000 |-----------------------|             |
                           |     Интерфейс BIOS    |             |
                     00040 |-----------------------|             |
                           |   Векторы прерываний  |             V
                     00000  -----------------------------------------

               Рис.3-2. Использование памяти пользователя MS/PC-DOS

         Нерезидентная часть  обрабатывает внутренние команды MS-DOS (DIR,
         COPY,  SET и др.) и содержит загрузчик программ.  Он используется
         для  загрузки  программ либо при обращении к COMMAND.COM (в ответ
         на внешние команды) или по запросу программы пользователя.  Позд-
         нее в этой главе будет показано, как одна программа может исполь-
         зовать эту возможность для загрузки других программ или  перекры-
         тия программ.
             Раздел Рис.3-2,  помеченный как "Резидентные программы",  со-
         держит резидентные программы,  завершаемые и оставляемые в памяти
         (TSR),  такие как, например, программа "Borland's Sidekick". Раз-
         мещение памяти,  показанное на Рис.3-2, применяется для TSR, заг-
         ружаемых из файла AUTOEXEC.BAT, или непосредственно при инициали-
         зации системы. В главе 4 программы TSR рассматриваются более под-
         робно.
             Раздел "Драйверы устройств" относится к устанавливаемым драй-
         верам устройств,  т.е. к тем драйверам, которые указываются с по-
         мощью команды DEVICE = в файле CONFIG.SYS.  Устанавливаемые драй-
         веры устройств являются предметом рассмотрения главы  6. Драйверы
         устройств, назначаемые по умолчанию и применяемые в системе, при-
         ведены в разделе "Интерфейс BIOS",  где они используются во время
         загрузки или инициализации системы MS-DOS.
             "Ядро MS-DOS" - это раздел MS-DOS,  который обрабатывает раз-
         личные  функции  MS-DOS,  такие, например, как функция прерывания
         21h. Этот раздел является "мостом" между программами пользователя
         или COMMAND.COM и различными драйверами устройств,  а также аппа-
         ратными средствами.
             Раздел "Векторы прерываний" содержит  описание  256  векторов
         прерываний системы.
             Оставшаяся область - это "Нерезидентная  область   программы"

                                      - 3-5 -
         (TPA). (Название TPA восходит ко  временам  операционной  системы
         CP/M  -  прародительнице MS-DOS).  Эта область,  куда загружаются
         программы пользователя, и которой мы и будем заниматься далее.
             В некотором  смысле Рис.3-2 не совсем точно отражает действи-
         тельную ситуацию.  Не все элементы, показанные на нем, имеют свой
         собственный блок памяти,и, наоборот, имеются элементы, которые не
         показаны  на Рис.3-2,  но имеют свои отдельные блоки памяти. Рас-
         смотрим сначала более  подробно,  каким методом пользуется MS-DOS
         для организации своих собственных областей TPA.

                               Цепочки памяти MS-DOS

             Управление памятью MS-DOS начинается  при  загрузке  MS-DOS.
         Все блоки памяти MS-DOS либо свободны,  либо распределены, начи-
         ная с  блока управления памятью (MCB - memory control block). Эти
         блоки управления,  показанные на Рис.3-3,  идентифицируют  тип  и
         размер  блока  памяти и программу (или процесс),  которая владеет
         им.
             Два типа блоков управления памятью являются цепочками блоков,
         тип  которого  есть 4Dh,  и конечный блок цепочки - тип 5Ah.  Тип
         блока хранится в первом байте блока MCB.
             Следующие два  байта  в  блоке  MCB являются словом,  которое
         идентифицирует владельца блока памяти.  Значение нуль  указывает,
         что блок нераспределен или свободен.  Если поле владельца ненуле-
         вое, то это значит,  что блок  распределен.  Это  слово  содержит
         идентификатор  владельца процесса (PID - process identifier). PID
         для процесса пользователя получается от адреса  сегмента, взятого
         из  сегмента программного префикса (PSP - program segment prefix)
         данного процесса.
             Четвертый и пятый байты в MCB являются словом, которое содер-
         жит размер блока памяти,  следующего за этим блоком.  Этот размер
         выражается  в  параграфах (блоки по 16 байт) и не включает размер
         самого блока MCB. Оставшиеся 11 байтов MCB не определены.
             Несмотря на то, что полный список блоков управления часто от-
         носят к цепочке распределения памяти,  блоки MCB,  на самом деле,
         не  редактируются вместе,  MCB только указывает на распределенный
         блок памяти.  Вернее,  каждый блок MCB непосредственно следует  в
         памяти перед блоком,  которым он управляет.  Если MCB и соответс-
         твующий ему блок памяти не последние в цепочке, то за ними непос-
         редственно следуют другой MCB и блок памяти.
             Начиная от данного MCB,  адрес сегмента следующего MCB в  це-
         почке  получается  путем сложения размера (в параграфах) текущего
         блока с текущим адресом сегмента MCB,  плюс 1.  При этом  способе
         может  быть просмотрена вся цепочка MCB,  но только в прямом нап-
         равлении. Начиная от данного MCB, можно определить адрес предыду-
         щего MCB. Как затем можно узнать, какие блоки находятся в памяти?
             Функция MS-DOS с номером 52h (прерывание  int  21h)  является
         недокументируемой функцией,  которая возвращает указатель на спи-
         сок внутренних значений MS-DOS.  Указатель  возвращается  в  паре
         ES:BX.  Как раз перед этим списком в слове, указываемом значением
         ES:[BX - 2], находится адрес первого MCB. Из этой начальной точки
         может быть определена вся цепочка MCB.
             Эти способы  использованы в программе SHOWMEM (отобразить па-
         мять), приведенной в листинге 3-1.


                                      - 3-6 -
                      Адрес  Тип  Владелец  Размер
                      0А00:0.---------------------------------------.
                            | 4D |  0008  |  1600  |                |
                      0A01:0|---------------------------------------|
                            |                                       |
                            | Распределенный блок, владельцем кото- |
                            |         рого является MS-DOS          |
                      2001:0|---------------------------------------|
                            | 4D |  2013  |  0010  |                |
                      2002:0|---------------------------------------|
                            |                                       |
                            | Распределенный блок, владельцем кото- |
                            |       рого является процесс 2013      |
                      2012:0|---------------------------------------|
                            | 4D |  2013  |  0500  |                |
                      2013:0|---------------------------------------|
                            |                                       |
                            | Распределенный блок, владельцем кото- |
                            |       рого является процесс 2013      |
                      2513:0|---------------------------------------|
                            | 5A |  0000  |  7AEC  |                |
                      2514:0|---------------------------------------|
                            |                                       |
                            |  Cвободный блок (владелец - MS-DOS).  |
                            |Содержит остаток в верхней части памяти|
                            |                                       |
                      9FFF:F ---------------------------------------
                     Рис. 3-3. Блоки управления памятью MS-DOS

         Листинг 3-1 содержит как исходный файл SHOWMEM.ASM, так и заголо-
         вок  файла  PCP.INC  (который мы рассмотрим несколько подробней).
         Рис.3-4 изображает результаты работы программы SHOWMEM.  Подпрог-
         рамма  ShowMCBInfo  в программе SHOWMEM.ASM отображает содержимое
         блока MCB. Процедура main содержит коды для размещения начального
         блока и, после метки show_mem, вычислительные операции для нахож-
         дения следующего блока в цепочке.  Дополнительные коды в  подпрог-
         рамме ShowMCBOwner могут совсем не иметь смысла.  Эти коды исполь-
         зуются для отображения имени процесса,  который владеет блоком па-
         мяти, и поясняются в следующих разделах.
             Рассмотрев Рис.3-4, можно усвоить несколько интересных момен-
         тов.  В частности, можно увидеть,  что автор программы загрузил в
         память три резидентные программы: RETRIEVE (восстановление), MODE
         (режим) и SWITCH (переключатель). На Рис.3-4 можно также увидеть,
         что программа SHOWMEM имеет очень большой выделенный для нее блок
         памяти - 555 Кбайт!  Кроме того,  можно также увидеть, что каждая
         загруженная программа имеет два распределенных для нее блока  па-
         мяти. Вот это последнее обстоятельство мы первым и объясним.


                                      - 3-7 -
         1   SM-ShowMem, Version 1.00  c  Copyright 1988

         2   MCB    Size   Owner  Command Line
             -----------------------------------------------------------
             0A01   08D7   0008   DOS
             12D9   00D3   12DA   [ SHELL ]
             13AD   0003   0000   [ available ]
             13B1   0032   12DA   [ SHELL ]
             13E4   0004   13EA   c:\bin\RETRIEVE.COM
             13E9   00A9   13EA   c:\bin\RETRIEVE.COM
             1493   000F   14A4   S:\MODE.COM\*
             14A3   0017   14A4   S:\MODE.COM\*
             14BB   0010   14CD   c:\ws2000\SWITCH.COM
             14CC   0018   14CD   c:\ws2000\SWITCH.COM
             14E5   0011   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE
             14F7   8B08   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE

             <<<------------- End of Memory Block List ------------->>> 3

             Рис. 3-4. Пример отображения результатов работы программы
                                     SHOWMEM:

         1 -  программа показа памяти - ShowMem,  версия 1.00,  авторское
         право 1988;  2 - блок управления памятью,  размер, владелец, ко-
         мандная строка; 3 - конец списка блоков памяти.

         Листинг 3-1. SHOWMEM - программа отображения блоков памяти MS-DOS
         ----------------------------------------------------------------

                                  SHOWMEM.ASM
         PAGE    60,132
         ; **** SHOWMEM *************************************************
         ; ShowMem - Отображение блоков управления памятью, отредактиро-
         ;           ванных MS-DOS
         ; Этот файл создает программу SM.EXE
         ;
         ;***** ВКЛЮЧЕНИЯ ИЛИ ЭКВИВАЛЕНТЫ *******************************
         ;
         INCLUDE stdmac.inc
         INCLUDE psp.inc
         ;
         BlocMCB EQU 4Dh                     ; тип цепочечного MCB
         LastMCB EQU 5Ah                     ; тип последнего MCB
         FreeMCB EQU 0000h                   ; владелец свободного MCB
         ;
         NameSig EQU 0001h                   ; сигнатура имени процесса
         ;
         ; **** КОМПОНЕНТЫ СЕГМЕНТОВ DGROUP (ДАННЫЕ) ********************
         ;
         _DATA   SEGMENT BYTE PUBLIC 'DATA'
         _DATA   ENDS
         ;
         STACK   SEGMENT PARA STACK
                 dw      1024 dup (?)        ; стек 2 Кбайт
         STACK   ENDS
         ;
         DGROUP  GROUP   _DATA, STACK

                                      - 3-8 -
         ;
         ;**** ПАМЯТЬ ДАННЫХ ИЛИ ШАБЛОНЫ ********************************
         ;
         _DATA   SEGMENT BYTE PUBLIC 'DATA'
         ;
         ; Текстовые сообщения для отображения формата в виде:
         ;
         ; "MCB     Size    Owner    Coommand Line"
         ; "------------------------------------------------------------"
         ; "xxxx    xxxx    xxxx     cccccccc..."
         ; "<<<--------------- End of Memory Block List ------------->>>"
         $Title  db  CR,LF
                 db  'SM-ShowMem, Version 1.00,  c  Copyright 1988'
                 db  CR,LF,CR,LF
                 db  'MCB    Size   Owner   Command Linr'
                 db  CR,LF
                 db  '--------------------------------------------'
                 db  '----------------'
                 db  CR,LF,'$'
         $Space  db  '   $'
         $Free   db  '[ available ]$'
         $DOS    db  'DOS$'
         $Shell  db  '[ SHELL ]$'
         $MCBad  db  CR,LF
                 db  '********** Error in MCB Chains : Aborting List'
                 db  ' **********'
         $End    db  CR,LF
                 db  '<<< ************ End of Memory Block List'
                 db  ' ------------ >>>'
         $Crlf   db  CR,LF,'$'
         ;
         ; Шаблоны структур
         mcb     STRUC               ; структура блока управления памятью
                 TypeMCB  db     ?   ; тип блока
                 OwnerMCB dw     ?   ; владелец блока
                 SizeMCB  dw     ?   ; размер блока
         mcb     ENDS
         ;
         _DATA   ENDS
         ;
         ; **** Здесь начинается код программы **************************
         ;
         _TEXT   SEGMENT byte public 'code'
                 ASSUME  cs:_TEXT, ds:DGROUP, es:DGROUP, ss:DGROUP
         ;
                 EXTRN   bin2hex:NEAR ; шестнадцатиричное отображение
         main    PROC    FAR
                 mov     ax,DGROUP    ; установка сегмента данных
                 mov     ds,ax
         ;
         ; Отображение заголовка для списка блоков памяти
                 @DisStr $Title
         ;
         ; Нахождение начала очереди блоков памяти
                 mov     ah,52h       ; получение параметров DOS
                 int     21h          ; возврат указателя в ES:BX
                 sub     bx,2         ; указывает на 1-й адрес MCB

                                      - 3-9 -
                 mov     ax,word ptr es:[bx] ; получение начального блока
                 mov     es,ax
                 xor     di,di        ; очистка индекса
                 cmp     byte ptr es:[di].TypeMCB,BlocMCB
                 jne     bad_chain    ; выход, если не начало цепочки
         ;
         ; Цикл для нахождения и отображения каждого блока памяти
         show_mem:
                 call    ShowMCBInfo  ; дамп содержимого MCB
                 cmp     byte ptr es:[di].TypeMCB,LastMCB
                 je      done         ; выход, если конец цепочки
                 mov     ax,es        ; вычисление следующего адреса
                 add     ax,es:[di].SizeMCB ; добавление размера блока
                 inc     ax           ; плюс 1 для себя
                 mov     es,ax        ; начало нового блока
                 cmp     byte ptr es:[di].TypeMCB,LastMCB
                 je      show_mem     ; продолжение, если правильный тип
                 cmp     byte ptr es:[di].TypeMCB,BlocMCB
                 je      show_mem     ; продолжение, если правильный тип
         ;
         bad_chain:                   ; ошибка в MCB "chains"
                 @DisStr $MCBad       ; завершающее сообщение
                 @DisStr $Crlf
                 mov     al,1         ; завершение без ошибки
                 @ExitToDOS           ; завершение программы
         ;
         done:   @DisStr $End         ; завершающее сообщение
                 @DisStr $Crlf
                 mov     al,0         ; нормальное завершение
                 @ExitToDOS           ; завершение программы
         ;
         main    ENDP
         ;
         ; **** ShowMCBInfo *********************************************
         ; ShowMCBInfo отображает блоки, адресуемые с помощью ES:DI как
         ; блоки управления памятью MS-DOS. Формат отображения показан
         ; выше.
         ;
         ShowMCBInfo PROC    NEAR
                 mov     ch,04        ; отображение числовых данных
                 mov     ax,es        ; адрес MCB
                 call    bin2hex
                 @DisStr $Space
                 mov     ax,es:[di].SizeMCB ; связанный блок
                 call    bin2hex
                 @DisStr $Space
                 mov     ax,es:[di].OwnerMCB ; владелец
                 push    ax           ; сохранение владельца
                 call    bin2hex
                 @DisStr $Space
                 pop     ax
                 cmp     ax,FreeMCB   ; блок освобожден?
                 je      is_free      ; да, выполнять имя не надо
                 call    ShowMCBOwner ; нет, отображение владельца
                 jmp     Info_Exit
         ;

                                      - 3-10 -
         is_free:
                 @DisStr $Free        ; отметить блок как свободный
         Info_exit:
                 @DisStr $Crlf
                 ret
         ShowMCBInfo ENDP
         ;
         ; **** ShowMCBOwner ********************************************
         ; ShowMCBOwner извлекает и отображает владельца MCB DOS из
         ; соответствующей строки среды. ES:DI указывает на допустимый
         ; MCB с ненулевым полем владельца.
         ;
         ShowMCBOwner PROC   NEAR
                 push    es           ; сохранение адреса MCB
                 push    di           ; сохранение для приборки
         ;
         ; Получение PID (адреса PSP), который владеет этим блоком памяти
                 mov     ax,es:[di].OwnerMCB ; адрес PSP владельца
                 mov     es,ax
               cmp     es:[di].PSPExitInt,PSPSignature ; допустимый PSP?
                 je      Owner_PID    ; да, владелец имеет PID
         ;
         ; Без PSP владелец должен быть ядром DOS
         Owner_DOS:
                 @DisStr $DOS         ; владелец MS-DOS
                 jmp     Owner_Exit   ; все выполнено
         ;
         ; Извлечение сегмента среды процесса из PSP
         Owner_PID:
                 mov     ax,es:[di].PSPEnvironment ; да, получ. адр.среды
                 push    ax           ; сохранение сегмента среды
         ;
         ; Получение размера сегмента среды
                 dec     ax           ; MCB среды
                 mov     es,ax
                 mov     cx,es:[di].SizeMCB ; получение размера среды
                 shl     cx,1               ; преобразование параграфов
                 shl     cx,1               ; в байты
                 shl     cx,1
                 shl     cx,1
         ;
         ; Продолжение поиска имени процесса по ES:DI, длине CX
         ; Каждая переменная среды завершается нулевым байтом.
         ; Список переменных завершается другим нулевым байтом
                 cld                  ; поиск вперед
                 pop     es           ; восстановление среды
                 xor     al,al        ; поиск значения
         search:
                 repne   scasb        ; поиск для ASCIIZ
                 jne     Owner_DOS    ; останов, если выход за границу
                 scasb                ; конец списка строк
                 jne     search       ; продолжить, если больше
         ;
         ; Проверка наличия "Сигнатуры", продолжающей (возможные) имена
                 mov     si,di        ; передача в SI
                 push    ds           ; сохранение сегмента строк
                 push    es           ; передача ES в DS
                 pop     ds

                                      - 3-11 -
                 lodsw                ; чтение предшествующего слова
                 cmp     al,NameSig   ; проверка действительного имени
                 je      show_name    ; имя допустимое
         ;
         ; Без действительного имени владелец должен иметь имя SHELL
                 pop     ds
                 @DisStr $Shell       ; владелец имеет имя shell
                 jmp     Owner_Exit
         ; ES:DI указывает на допустимое (0 завершенное) имя процесса
         show_name:
                 lodsb                ; чтение символа,
                 cmp     al,0         ; одновременно проверка
                 je      Owner_POP    ; окончания, и
                 @DisStr al           ; отображение
                 loop    show_name
         Owner_Pop:
                 pop     ds
         Owner_Exit:
                 pop     di
                 pop     es
                 ret
         ShowMCBOwner ENDP
         ; ***** КОНЕЦ ПРОГРАММЫ : КОНЕЦ ФАЙЛА **************************
         ;
         _TEXT   ENDS
                 END     main
                                      ; PSP.INC
         ;***************************************************************
         ; ФАЙЛ ВКЛЮЧЕНИЯ ОПИСАНИЙ PSP
         ;***************************************************************
         ;
         PSPSignature   EQU   020cdh  ; слово, начинающее все PSP
         ;
         ProgramSegmentPrefix STRUC
         PSPExitInt     dw    ?       ; прерывание выход int 20h
         PSPMemTot      dw    ?       ; вершина памяти
         PSPResvr1      db    ?
         PSPDOSCall     db    5 dup (?) ; вызов MS-DOS
         PSPTerminate   db    ?       ; адрес завершения
         PSPControlC    dd    ?       ; адрес control-C
         PSPCritical    dd    ?       ; адрес критической ошибки
         PSPParent      dw    ?       ; владелец PSP
         PSPHandleTable db   20 dup (?);таблица описателей по умолчанию
         PSPEnvironment dw    ?       ; адрес среды
         PSPStack       dd    ?       ; начальные значения стека
         PSPHandleSize  dw    ?       ; размер таблицы описателей
         PSPHandlePntr  dd    ?       ; адрес таблицы описателей
         PSPResvr2      db   24 dup (?)
         PSPDOSInt      db    3 dup (?) ; прерывание 21h или возврат
         PSPResvr3      db    9 dup (?)
         PSPFCB1        db   16 dup (?) ; блок управления файлом
         PSPFCB2        db   16 dup (?) ; блок управления файлом
         PSPResvr4      db    4 dup (?)
         PSPCommandLen  db    1       ; длина командной строки
         PSPCommandBuf  db  127 dup (?) ; текст командной строки
         ProgramSegmentPrefix ENDS
         ________________________________________________________________

                                      - 3-12 -

                         Блок операционной среды программы

             При загрузке  программы в память MS-DOS всегда присоединяет в
         начало программы блок операционной среды (далее  просто "среда"),
         запоминая в нем владельца блока памяти. На Рис.3-4 он показан как
         первый небольшой блок,  который соотносится с каждой  программой.
         Блок среды программы содержит собственную копию операционной сре-
         ды MS-DOS.  Среда MS-DOS  представляет собой область,  в  которой
         запоминаются    PATH    (путь),   COMSPEC   (спецификация   файла
         COMMAND.COM) и PROMPT (приглашения) вместе с  любыми переменными,
         назначаемыми по команде SET (установить). Общей формой переменной
         среды является:  NAME = строка .  Формат блока среды  приведен  в
         примере, показанном на Рис.3-5.
             Из Рис.3-5  можно  видеть,  что  каждый элемент в блоке среды
         представляет собой строку в коде ASCII  (American  Standard  Code
         for  Information  Interchange  - Американский стандартный код для
         обмена информацией),  завершаемую нулевым байтом.  (Этот стандарт
         фирмой "Майкрософт" назван ASCIIZ).  Весь список элементов закан-
         чивается  еще  одним нулевым байтом,  показанным на Рис.3-5 в ка-
         честве седьмого элемента.  Элементы, предшествующие этому маркеру
         "конец списка", отображаются всякий раз при использовании команды
         SET.  А что из себя представляют два  элемента,  следующие  после
         маркера "конец списка"?
             Недокументируемая возможность MS-DOS версии 3  и  последующих
         версий  состоит в том,  что всякий раз при запуске процесса с по-
         мощью COMMAND.COM либо непосредственно,  либо в ответ на  функцию
         EXEC (выполнить),  имя процесса помещается в блок среды процесса.
         На Рис.3-5 последние два элемента перед частью  "не используемая"
         являются  этим недокументируемым именем процесса.  Имени процесса
         предшествует слово 0001h.  Имя содержит собственное  имя  и  путь
         процесса  и запоминается в формате ASCIIZ.  Из Рис.3-5 можно уви-
         деть, что этот блок среды относится к процессу SHOWMEM.
             Одним из  элементов,  не представленных на Рис.3-5,  является
         общий размер блока среды. В отличие от основной среды MS-DOS, чей
         размер может управляться с помощью параметра,  устанавливаемого в
         файле CONFIG.SYS,  размер блока среды  процесса  определяется  во
         время  загрузки  программы  путем помещения в него только текущей
         части среды.
             Сравните в примере отображения SHOWMEM на Рис.3-4 800-байтный
         размер среды DOS (второй элемент с  именем  "SHELL")  со  средами
         RETRIEVE  и SHOWMEM в 64 и 272 байта, соответственно. Несмотря на
         то,  что DOS должна резервировать 800 байт, при загрузке RETRIEVE
         впереди  файла AUTOEXEC.BAT среда содержит меньше,  чем 64 байта.
         После завершения файлом AUTOEXEC.BAT установок PATH и PROMPT и из-
         менения других переменных, среда должна увеличиться приблизитель-
         но до 200 байт.
             Имеются две причины получения собственного блока среды каждым
         процессом  при его создании.  Первая - это уменьшение вероятности
         того, что процесс будет испорчен средой его владельца - критичес-
         кое требование, если владелец процесса - файл COMMAND.COM. Вторая
         связана с тем,  что процесс-владелец должен управлять средой, пе-
         редаваемой порождаемому процессу,  что, в свою очередь, позволяет
         процессу-владельцу управлять поведением порожденного  процесса. К
         этой теме мы вернемся снова, когда будем рассматривать загрузку и
         выполнение программы.  Мы также вернемся  к  нерешенному  вопросу
         большого размера блока памяти SHOWMEM. Не забудьте эту  проблему.

                                      - 3-13 -
         .------------------------------.
         | COMSPEC = C:\COMMAND.COM\ | 0 |
         |----------------------------------------------.
         | INCLUDE = C:\msc\include;c:\masm\include | 0 |
         |-----------------------------------------------
         | LIB = c:\msc\lib | 0 |
         |-----------------------
         | ECHO = OFF | 0 |
         |-------------------.
         | PROMPT = $p$g | 0 |
         |-------------------------------------------------------------.
         |PATH = C:\DOS;C:\BIN;C:\BAT;C:\UTILS;C:\MASM;C:\WIN;C:\WS| 0 |
         |--------------------------------------------------------------
         | 0 |
         |-------.
         | 0001h |
         |-----------------------------------.
         | C:\GUIDE\EXAMPLES|SHOWMEM.EXE | 0 |
         |------------------------------------
         | Не используемая  |
         |                  |
         |/\/\/\/\/\/\/\/\/\|

                                Рис.3-5. Блок среды

         когда мы вернемся к ней после  накопления  основных  сведений  по
         рассматриваемым вопросам.

                                  Процессы MS-DOS

             Мы начали эту главу с описания того, как все пространство па-
         мяти системы MS-DOS форматируется на разделы для MS-DOS,  BIOS  и
         системных функций аппаратных средств.  Затем мы видели,  как раз-
         дел,  управляемый MS-DOS, организуется в различные области, вклю-
         чая  нерезидентную  область  программ  или TPA (transient program
         area). Мы также увидели, как TPA управляется посредством использо-
         вания  блоков  управления памятью,  и что каждый процесс включает
         два блока памяти:  блока среды и блока, который мы будем называть
         "блок процесса".  Теперь мы можем перейти к обзору блока процесса
         и рассмотреть отдельные компоненты,  которые включает в себя про-
         цесс MS-DOS.

                             Контекст процесса MS-DOS

             Рис.2-3 в  главе 2 дает нам представление о внутренней струк-
         туре процесса MS-DOS для процессов типа .EXE и  .COM.  Теперь  мы
         можем объединить это с тем,  что уже изучили, для представления в
         памяти более детального образа процесса MS-DOS.  Это новое предс-
         тавление показано на Рис. 3-6.
             На Рис.3-6 имеется  много  деталей,  которые  нам  необходимо
         рассмотреть.  Мы  начнем с сегмента программного префикса или PSP
         (program segment prefix).

                           Сегмент программного префикса

             Сегмент программного префикса (PSP), введенный в главе 2, яв-
         ляется  в некотором смысле "краеугольным камнем" процесса MS-DOS.

                                      - 3-14 -
         Адрес сегмента PSP обеспечивает идентификатор процесса и служит в
         качестве  идентификатора  блока памяти процесса.  Устанавливаемый
         всегда в начале блока процесса PSP также служит в качестве  "хра-
         нилища" для большого количества ценной информации.
             В данном документе PSP представлен в трех представлениях: как
         графическое изображение на Рис.3-7; как подробное описание в таб-
         лице 3-1 и,  наконец, как описание структуры макроассемблера MASM
         STRUC в PSP.INC, приведенной в листинге 3-1. Рисунок дает возмож-
         ность быстрого размещения информации, таблица обеспечивает глуби-
         ну информации, а листинг показывает смещения, необходимые при ис-
         пользовании в программах пользователя.
             Даже беглый взгляд на Рис.3-7 и таблицу 3-1 открывает обилие
         информации, которая может быть полезна программисту.  Однако, не-
         которые элементы PSP требуют гораздо большего пояснения.

            Процесс .COM       Нижние адреса памяти      Процесс .EXE

         |\/\/\/\/\/\/\/\/\/|                        |\/\/\/\/\/\/\/\/\/|
         |COMMAND или преды-|    Предыдущий блок     |COMMAND или преды-|
         |  дущей программы |                        |  дущей программы |
          ------------------                          ------------------
         .------------------.                        .------------------.
         ||  4D/PSP/size/  ||        MCB среды       ||  4D/PSP/size/  ||
         ||----------------||                        ||----------------||
         ||  имя = строка  ||       блок среды       ||  имя = строка  ||
          ------------------                          ------------------
         .------------------.                        .------------------.
         ||  5A/PSP/size/  ||       MCB процесса     ||  4D/PSP/size/  ||
         ||----------------||                        ||----------------||
         ||  PSP программы ||      блок процесса     ||  PSP программы ||
         || - - - - - - - -||                        || - - - - - - - -||
         || Код программы  ||                        || Код программы  ||
         ||   и данные     ||                        || - - - - - - - -||
         || - - - - - - - -||                        || Стек или данные||
         ||                ||                         ------------------
         ||                ||                        .------------------.
         ||"Ни себе, ни лю-||          свободный MCB |   5A/0000/size/  |
         ||       дям"     ||                        |------------------|
         ||                ||                        |                  |
         || - - - - - - - -||                        |                  |
         ||     Стек       ||                        |   Доступна для   |
         ||----------------||                        |   использования  |
         || Недоступна для ||  Неиспользуемая память |                  |
         ||  использования ||                        |                  |
         |/\/\/\/\/\/\/\/\/\|                        |/\/\/\/\/\/\/\/\/\|

                               Верхние адреса памяти
           -------
          ||     || - Память, распределяемая/владеющая процессом
           -------

                    Рис.3-6. Контекст процесса MS-DOS в памяти

                               Адреса завершения PSP

             Таблица 3-1  показывает  три "адреса завершения",  хранимые в
         байтах от 0Ah до 15h PSP. Как уже объяснялось выше, эти копии:

                                      - 3-15 -
         адреса завершения  программы,  адреса  выхода  по  нажатию клавиш
         Control-Break и адреса выхода по критической ошибке выбираются из
         действительных векторов прерываний, размещаемых в int 22h,int 23h
         и int 24h. Чтобы воздействовать на поведение системы во время си-
         туации завершения (такой как, например, выход по внутреннему пре-
         рыванию CONTROL-BREAK/CONTROL-C)  программисту требуется изменить
         основные векторы прерываний. Это можно сделать, используя для по-
         лучения и изменения этих адресов функции "Установить вектор" (Set
         Vector - код 25h) и "Получить вектор" (Get Vector - код 35h).

                           Таблица описателей файлов PSP

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

                                                         Таблица 3-1
                     Содержимое сегмента программного префикса
         ________________________________________________________________
         Шестнадцатиричные|
         -----------------|                  Содержимое
         смещение| размер |
         ________|________|______________________________________________
            00   |    2   | Прерывание int 20h. Содержит инструкцию пре-
                 |        | рывания int 20h (байты CD 20 - шестнадцати-
                 |        | ричные значения). Устаревшее использование.
                 |        | Программист вместо завершения должен исполь-
                 |        | зовать функцию 4Ch, прерывание int 21h.
         ________|________|______________________________________________
            02   |    2   | Вершина памяти. Содержит адрес сегмента, сле-
                 |        | дующего за памятью программы. Это может быть
                 |        | адрес старой памяти DOS (такой как А000) или
                 |        | адрес следующего доступного блока управления
                 |        | памятью.
         ________|________|______________________________________________
            04   |    1   | Зарезервирован.
         ________|________|______________________________________________
            05   |    5   | Длинный вызов диспетчера функций MS-DOS. Со-
                 |        | держит длинный переход к диспетчеру функций
                 |        | MS-DOS для использования с программами типа
                 |        | CP/M. Устаревшее использование. Программы
                 |        | должны вместо вызова MS-DOS использовать пре-
                 |        | рывание int 21h.
         ________|________|______________________________________________
            06   |    2   | Доступная память. Часть смещения длинного вы-
                 |        | зова также содержит количество байтов, дос-
                 |        | тупных в кодовом сегменте программы.
         ________|________|______________________________________________
            0A   |    4   | Адрес завершения программы. Копия адреса пре-
                 |        | рывания int 22h (IP,CS), по которому переда-

                                      - 3-16 -
         ________________________________________________________________
         Шестнадцатиричные|
         -----------------|                  Содержимое
         смещение| размер |
         ________|________|______________________________________________
                 |        | ется управление, когда вводятся Control-Break
                 |        | или Control-C.
         ________|________|______________________________________________
            0E   |    4   | Адрес выхода Control-Break. Копия адреса пре-
                 |        | рывания int 23h (IP,CS), по которому переда-
                 |        | ется управление, когда вводятся Control-Break
                 |        | или Control-C.
            12   |    4   | Адрес выхода по критической ошибке. Копия ад-
                 |        | реса прерывания int 24h (IP,CS), по которому
                 |        | передается управление, когда во время обра-
                 |        | ботки обнаруживается критическая ошибка.
         ________|________|______________________________________________
            16   |    2   | Префикс программного сегмента владельца. Это
                 |        | сегментный адрес сегмента программного префи-
                 |        | кса владельца. Для процессов, не имеющих вла-
                 |        | дельца, это адрес текущего PSP.
         ________|________|______________________________________________
            18   |   14   | Таблица описателей файла. Содержит 20 одиноч-
                 |        | ных байтов "обработки" (индикаторов) в табли-
                 |        | це файлов системы. Первыми 5 из них являются:
                 |        | STDIN, STDOUT, STDERR, AUXIO и LSTOUT. Смотри
                 |        | текст для более подробного объяснения.
         ________|________|______________________________________________
            2C   |    2   | Адрес среды. Адрес сегмента блока среды про-
                 |        | цесса.
         ________|________|______________________________________________
            2E   |    4   | Память переключателя стека. Используется для
                 |        | хранения стекового сегмента процесса и указа-
                 |        | теля (SS:SP), когда процесс выполняет опера-
                 |        | ции в стеке MS-DOS.
         ________|________|______________________________________________
            32   |    2   | Счетчик описателей. Максимальное количество
                 |        | элементов, допускаемое в таблице описателей
                 |        | файла. По умолчанию принимается значение 20.
         ________|________|______________________________________________
            34   |    4   | Адрес таблицы описателей.Длинный указатель на
                 |        | таблицу описателей файла.По умолчанию в теку-
                 |        | щем PSP принимается значение смещения 18 (ше-
                 |        | стнадцатиричное значение).
          _______|_______________________________________________________
            38   |   18   | Зарезервировано.
         ________|________|______________________________________________
            50   |    3   | Прерывание диспетчера функций. Содержит код
                 |        | для прерывания int 21h вызова диспетчера фун-
                 |        | кций MS-DOS, следующего по выходу far RET.
         ________|________|______________________________________________
            53   |    2   | Зарезервировано.
         ________|________|______________________________________________
            55   |    7   | Расширение блока управления файлом. Поля рас-
                 |        | ширения для блока #1 управления файлом. Уста-
                 |        | ревшее использование. Программы должны ис-
                 |        | пользовать вместо описателя файла. Для полу-
                 |        | чения более подробной информации по FCB (File

                                      - 3-17 -
         ________________________________________________________________
         Шестнадцатиричные|
         -----------------|                  Содержимое
         смещение| размер |
         ________|________|______________________________________________
                 |        | control block - блок управления файлом) обра-
                 |        | титесь к руководствам по MS-DOS).
         ________|________|______________________________________________
            5C   |   10   | Блок управления файлом номер 1. Содержит не-
                 |        | открытый блок FCB #1. Устаревшее использова-
                 |        | ние и в результате может привести к разруше-
                 |        | нию FCB #2 и длины командной строки. Пути
                 |        | имен файлов не поддерживаются. Вместо этого
                 |        | программы должны использовать описатели   фай-
                 |        | лов. Для получения более подробной информации
                 |        | по FCB, обратитесь к руководствам по MS-DOS.
         ________|________|______________________________________________
            6C   |   10   | Блок управления файлом номер 2. Содержит не-
                 |        | открытый блок FCB #2. Устаревшее использова-
                 |        | ние и в результате может привести к разруше-
                 |        | нию параметров командной строки. Вместо это-
                 |        | го программы должны использовать описатели
                 |        | файлов. Для получения более подробной инфор-
                 |        | мации по FCB, обратитесь к руководствам по
                 |        | MS-DOS.
         ________|________|______________________________________________
            7C   |    4   | Зарезервировано.
         ________|________|______________________________________________
             80  |   80   | Дисковая область передачи, назначаемая по
                 |        | умолчанию. Перекрывает при использовании
                 |        | строку текста командной строки.
         ________|________|______________________________________________
            80   |    1   | Длина командной строки. Длина текстовой стро-
                 |        | ки, которая была набрана следом за именем
                 |        | программы, минус любые переназначенные симво-
                 |        | лы или параметры.
         ________|________|______________________________________________
            81   |   7F   | Буфер командной строки. Текстовая строка, ко-
                 |        | торая была введена следом за именем программы.
                 |        | Символы переназначения (< и >) и их соответст-
                 |        | вующие имена файлов в этой области не появля-
                 |        | ются, т.к. переназначение прозрачно для прик-
                 |        | ладной программы.
         ________|________|______________________________________________

            Открытые описатели сохраняют свои показания  в  таблице  файлов
         системы. Неиспользуемые элементы в таблице помечаются шестнадцати-
         ричным значением 0FF. Первые пять обработок в таблице  описателей
         файлов зарезервированы за стандартными устройствами:  STDIN (стан-
         дартный ввод),  STDOUT (стандартный  вывод),  STDERR  (стандартная
         ошибка),  AUXIO (вспомогательный ввод-вывод) и LSTOUT (стандартный
         вывод на печать) и открываются при запуске процесса. Все показания
         отсчитываются от первоначального нулевого значения.
             Рис.3-8 показывает  состояние таблицы описателя  файла, приня-
         той по умолчанию,  сразу  же после успешного открытия файла myfile
         (мой файл).  Таблица  описателя   файла, принимаемая по умолчанию,
         является двадцатибайтовой  таблицей,   размещенной в PSP по смеще-
         нию 18 (шестнадцатиричное значение).   Этот  адрес запоминается  в

                                      - 3-18 -

         адресе таблицы описателя   при запуске процесса. В связи  с  тем,
         что первые  пять  обработок  зарезервированы за стандартными уст-
         ройствами, остается только 15 обработок, доступных для файлов или
         других устройств.
         .00h-----------02h-------------------05h-----------------------.
         |int 20h      |Вершина памяти  | 00 | Далекий вызов MS-DOS     |
          -------------|0Ah----------------------0Eh--------------------|
                       |Адрес завершения        |Адрес выхода Ctrl-Break|
                       |12h---------------------|16h--------------------
                       |Адр.вых. по крит. ошибке|PSP владельца|
         .18h-------------------------------------------------|
         |Таблица описателя файла                             |
         |----------------------------------------------------|
         |Таблица описателя файла (продолжение)               |
         |-------------------------------2Ch-----2Eh--------------------.
         |Таблица описателя файла(конец)|Среда  |Начальный адрес стека  |
          --------------32h-------------|34h----------------------------
                       |Счетчик описат. |Указатель таб.описат.|
         .38h-------------------------------------------------|
         |Зарезервированная область (длиной 40 байт)          |
          ----------------------------------------------------

         .50h-----------------53h----------55h----------------.
         |Функция int 21h    |Зарезерв.   |Расширение FCB     |
         |----------------------------5Ch---------------------|
         |Расширение FCB(продолжение)|Блок управл-я файлом #1 |
         |----------------------------------------------------|
         |Блок управления файлом #1 (продолжение)             |
         |----------------------------6Ch---------------------|
         |Блок управления файлом #1  |Блок управл-я файлом #2 |
         |----------------------------------------------------|
         |Блок управления файлом #2 (продолжение)             |
         |----------------------------7Ch---------------------|
         |Блок управления файлом #2  |Зарезервированная обл.  |
         |80Ch-81Ch-------------------------------------------|
         |Дл. |Буфер команд (длиной 127 байт)                 |
          ----------------------------------------------------

                              Рис.3-7. Структура PSP

             На Рис.3-8 значение описателя, возвращаемое при успешном вы-
         полнении функции OPEN,  равно 0005, которое означает, что файлу с
         именем  myfile назначен шестой элемент (вход) в таблице  описате-
         ля  файлов процесса. При  обращении шестой вход содержит значение
         03,  которое означает,  что файлу myfile был  назначен  четвертый
         вход  в  таблице файлов системы.  Рис.3-8 также демонстрирует ис-
         пользование первых  трех  описателей с  целью  показа  назначения
         для  одного и того же входа в системной таблице файлов нескольких
         обработок.  Максимальное количество входов  в  системную  таблицу
         файлов устанавливается с помощью предложения FILES = в файле кон-
         фигурации системы CONFIG.SYS.


                                      - 3-19 -
             .34h----------------------------.       .--------------.
             | Указатель таблицы описателя   |       | Описатель AX |
             | PS Segment:0018 (шестнадц.)   |       |  OPEN = 0005 |
              -------------------------------         --------------
                           |                               |
                            -------------------------------
                                                   |
                0     1      2      3      4     5 v     6     7
            .-------------------------------------------------------.
            |18h   |      |      |      |      |Таб.описател.файлов |
            | STDIN|STDOUT|STDERR| AUXIO|LSTOUT|myfile|(не использ.)|
            |  01  |  01  |  01  |  00  |  02  |  03  |  FF  |  FF  |
             -------------------------------------------------------
               |      |      |      |      |      |
               |-------------       |      |      |
               |Табл. файлов системы|      |      |
               |  .--------------.  |      |      |
               |  |  AUX      0  |<-       |      |
               |  |--------------|         |      |
                ->|  CON      1  |         |      |
                  |--------------|         |      |
                  |  PRN      2  |<--------       |
                  |--------------|                |
                  |  MYFILE   3  |<---------------
                  |--------------|
                  |Не использ.4  |
                   --------------

                      Рис.3-8. Таблица описателей файлов PSP

             В большинстве ситуаций пользователю никогда нет необходимости
         быть осведомленным об этих устройствах,  однако,  существуют  две
         ситуации, когда эти знания полезны.
             Первая ситуация возникает,  когда программа пользователя тре-
         бует больше описателей,  чем может быть открыто в  данное  время.
         Так как по умолчанию таблица описателей файлов поддерживает толь-
         ко 20 описателей и т.к. 5 описателей уже присвоены, то практичес-
         ки  невозможно  так далеко все предугадать.  Тем не менее,  чтобы
         обойти это ограничение,  программа должна установить  свои  собс-
         твенные  расширенные  таблицы описателей файлов,  как показано во
         фрагменте программы в листинге 3-2.
             При второй  ситуации листинг 3-2 предполагает,  что для прог-
         раммы использовано размещение новой таблицы и, кроме того,  пред-
         полагает,  что таблица была предварительно загружена с кодами 0FF
         (коды неиспользуемых  описателей).  Программа сначала  определяет
         ячейки  PSP,  используя  функцию  62h.  Из PSP находится размер и
         ячейки существующей таблицы описателей файлов,  и старая  таблица
         копируется в новую таблицу.  Новый адрес таблицы и ее размер сох-
         раняются в соответствующих полях PSP и обмен завершается.
             Другой возможностью, предоставляемой этим механизмом, являет-
         ся  то,  что программист теперь управляет переназначением ввода и
         вывода программы. В MS-DOS переназначение выполняется простым из-
         менением драйвера, связанного с конкретным устройством. Этот спо-
         соб даже работает для переназначения ввода и вывода, выполняемого
         со старыми, необрабатываемыми вызовами ввода и вывода (такими как
         функция 09h "Отобразить строку").
             Листинг 3-3  демонстрирует как устройство stdout (стандартный

                                      - 3-20 -
         вывод) переназначается  в  файл или устройство myfile (мой файл).
         Программа сначала открывает имя myfile и сохраняет описатель. За-
         тем она получает адрес PSP и из PSP получает адрес таблицы описа-
         телей.  Используя myfile в качестве индекса  таблицы  описателей,
         программа получает индекс таблицы файлов системы myfile и запоми-
         нает его в индексе,  назначенном для stdout (стандартный  вывод),
         выполняя переназначение.  Оставшаяся часть программы "поворачива-
         ет" процесс и заканчивает работу закрытием описателя myfile.

             Листинг 3-2. Фрагмент программы для переключения таблицы
                                 описателей файла
         ----------------------------------------------------------------

         ; Этот листинг передает таблицу описателей файла, назначенную
         ; по умолчанию, в область, адрес которой указывается в ES:DI.
         ; Размер новой таблицы подразумевается в CX. Подразумевается
         ; MS-DOS версии 3.xx (для функции "Get PSP Address" - получить
         ; адрес PSP). Регистры AX и BX не сохраняются.
         ;
                push    ds       ; сохранение DS
                push    si       ; сохранение SI
                push    di       ; сохранение смещения новой таблицы
                push    cx       ; сохранение размера новой таблицы
                mov     ah,62h   ; получение PSP
                int     21h      ; возврат PSP в BX
                mov     ds,bx    ; адрес PSP
         ;
         ; Получение размера и адреса текущей таблицы
                mov     bx,032h  ; адрес размера таблицы
                mov     cx,[bx]  ; получение размера таблицы
                push    ds       ; сохранение адреса PSP
                lds     si,[bx]2 ; получение адреса текущей таблицы
         ; Копирование старой таблицы из DS:DI на новое место по ES:DI
                cld              ; пересылка в прямом направлении
                rep     movsb    ; пересылка таблицы на новое место
         ;
         ; Восстановление размера и положения новой таблицы и обновление
         ; PSP
                pop     ds       ; восстановление адреса PSP
                pop     cx       ; восстановление размера новой таблицы
                pop     di       ; восстановление смещения новой таблицы
                mov     [bx]2,di ; запоминание смещения новой таблицы
                mov     [bx]4,es ; запоминание сегмента новой таблицы
                mov     [bx],cx  ; запоминание размера новой таблицы
                pop     si       ; восстановление первоначального SI
                pop     ds       ; восстановление первоначального DS
         ----------------------------------------------------------------

                Листинг 3-3. Фрагмент программы для переназначения
                                   StdOut в файл
         ----------------------------------------------------------------

         ; Этот листинг открывает описатель файла или устройства с
         ; именем "myfile" и заменяет описатель StdOut вновь открытым
         ; описателем. Вход подразумевается с DS и ES, указывающих на
         ; сегмент данных. Переменные следующих данных предполагаются
         ; определенными:
         ;
         StdOut  equ    1         ; код для описателя StdOut

                                     - 3-21 -
         Handle  dw     ?         ; новая переменная описателя
         Outhand db     ?         ; переменная описателя StdOut
         MyFile  db     'filename.ext',0
         ;
         ; Открытие описателя для файла/устройства, находящегося в
         ; myfile
                 lea    dx,MyFile ; имя
                 mov    al,2      ; доступ чтение/запись
                 mov    ah,03dh   ; функция OPEN - открыть
                 int    21h
                 jc     OpenError
                 mov    Handle,ax ; сохранение описателя
         ;
         ; Передача описателя файла/устройства в описателю StdOut.
                 push   es        ; сохранение ES
                 mov    ah,62h    ; получение PSP
                 int    21h
                 mov    es,bx     ; ES указывает на PSP
                 les    bx,es:[bx].PSPHandlePntr
         ;
         ; ES:BX теперь указывает на таблицу описателя файла
                 mov    al,es:[bx].StdOut ; чтение описателя StdOut и
                 mov    Outhand,al        ; сохранение
                 mov    di,Handle         ; считанного индекса описателя
                 mov    al,es:[bx+di]     ; считывание входа описателя
                 mov    es:[bx].StdOut,al ; запом-е как описателя StdOut
                 pop    es
         ;
         ; Восстановление первоначальной описателя StdOut
                 push   es        ; сохранение ES
                 mov    ah,62h    ; получение PSP
                 int    21h
                 mov    es,bx     ; ES указывает на PSP
                 les    bx,es:[bx].PSPHandlePntr
         ;
         ; ES:BX указывает теперь на таблицу описателя файла
                 mov    al,Outhand ; считывание описателя StdOut
                 mov    es:[bx].StdOut,al ; запоминание описателя StdOut
                 pop    es
         ;
         ; Закрытие переназначенного файла
                 mov    bx,Handle ; описателя для файла или устройства
                 mov    ah,03eh   ; функция CLOSE - закрыть
                 int    21h
         ----------------------------------------------------------------


                       SHOWMEM и указатель адреса среды PSP

             Другим полезным значением,  сохраняемым в PSP, является адрес
         сегмента блока среды процесса. Мы не возвращались к этому входу в
         связи с тем, что он требовал последующего разъяснения, но так как
         теперь мы обладаем полной информацией,  необходимой для понимания
         всей программы SHOWMEM, включая подпрограмму ShowMCBOwner, то:
             - найдите начальный блок управления памятью, используя преры-
         вание int 52h;
             - используйте поле владельца в блоке MCB  в  качестве  адреса
         PSP;
             - проверьте PSP путем проверки первых двух байтов для  преры-
         вания int 20h;
             - если владельцем MCB является PSP, то извлеките адрес среды.
         Если  PSP  не  является  владельцем,  то  владельцем  должна быть
         MS-DOS;
             - вычтите  единицу из адреса сегмента среды для получения MCB
         среды, и извлеките из него размер среды;
             - проверьте среду на наличие двойного нуля,  который сигнали-
         зирует о конце строк ASCIIZ;
             - проверьте процесс пользователя на наличие  сигнатуры 0001.
         Если сигнатура  0001  найдена,  то печатайте следующее имя.  Если
         сигнатура 0001 отсутствует,  то процесс должен  быть  COMMAND.COM
         или эквивалентным командным процессом;
             - если текущий MCB не является последним,  то найдите следую-
         щий MCB путем добавления размера блока (плюс 1) к адресу MCB;
             - повторите выполнение со второго шага.
             Программа SHOWMEM демонстрирует внутренние  взаимосвязи,  су-
         ществующие внутри DOS и показывает как можно перейти от блока уп-
         равления памятью к PSP,  к блоку среды и обратно к MCB среды, вы-
         бирая необходимые данные.

                          Функции для манипулирования PSP

             MS-DOS содержит функции,  относящиеся непосредственно к  пре-
         фиксу программного сегмента.  Эти функции перечислены в таб. 3-2.
         Для этих функций,  которые получают и устанавливают PSP,  текущий
         PSP определяется MS-DOS не по программному сегменту, выполняемому
         в данное время.
             Например, предположим, что выполняется программа MYPROG, ког-
         да  получает  управление  установленная  подпрограмма (TSR,  если
         угодно) и выдает вызов функции GET PSP (получить PSP -  функция с
         кодом  62h).  В  этом  случае  MS-DOS возвращает значение PSP для
         прерванной программы MYPROG. Это происходит потому, что после то-
         го  как  подпрограмма  резидентной  памяти  выполнит функцию Keep
         Process (сохранить процесс) или завершить и оставить резидентной,
         она еще некоторое время будет считаться активной.  MS-DOS считает
         последнюю загруженную программу текущей активной программой.
             Если важно, чтобы TSR имела доступ к своему собственному PSP,
         то  для этого может быть использована недокументированная функция
         SET PSP (установить PSP - функция с кодом 50h).  Когда TSR загру-
         жается  в  первый раз,  она должна сохранить значение своего PSP.
         Затем ,  когда TSR позднее  получит  управление,  PSP  прерванной
         программы может быть определен с помощью функции 62h (GET PSP-по-
         лучить PSP).  Это значение должно быть сохранено и  активизирован
         собственный  PSP  TSR с помощью функции 50h (SET PSP - установить
         PSP).  После выполнения TSR, она должна восстановить первоначаль-
         ный PSP с помощью функции SET PSP (установить PSP).

                                      - 3-23 -
                                                         Таблица 3-2
           Функции прерываний int 21h для сегмента программого префикса
         ________________________________________________________________
                 |
          Функция|                      Назначение
         ________|_______________________________________________________
           26h   | Создание блока PSP. Устаревшее использование
         ________|_______________________________________________________
           50h   | Установка текущего PSP. Недокументированная. BX содер-
                 | жит адрес сегмента действительного PSP. Эта функция
                 | заставляет новый PSP (BX) стать активным PSP для
                 | MS-DOS. Последовательные обращения к MS-DOS, ссылающи-
                 | еся к данным PSP, также как таблица описателя файла,
                 | будут использовать новый PSP.
         ________|_______________________________________________________
           51h   | Получить сегмент PSP. Недокументированная. Возвращает
                 | адрес текущего сегмента PSP в регистре BX. Это тоже
                 | самое, что и функция 62h, но она также доступна и в
                 | более ранних версиях MS-DOS 3.00. Ненадежна для вызова
                 | из TSR. Вместо этой функции рекомендуется использовать
                 | функцию 62h.
         ________|_______________________________________________________
           55h   | Получить копию PSP. Недокументированная. Функция почти
                 | идентична функции 26h. DX содержит адрес сегмента но-
                 | вого PSP. Однако, эта функция будет также устанавли-
                 | вать поле владельца нового PSP для адреса сегмента те-
                 | кущего PSP. Т.к. это недокументированная функция и по-
                 | лезна только при загрузке новой программы, то вместо
                 | нее рекомендуется использовать функцию EXEC (выпол-
                 | нить) с кодом 4Bh.
         ________|_______________________________________________________
           62h   | Получить текущий PSP. MS-DOS версии 3.00 и последующие
                 | версии. Возвращает адрес сегмента текущего PSP в ре-
                 | гистре BX.
         ________|_______________________________________________________


                  Файлы процессов MS-DOS: .EXE в сравнении с .COM

             Как известно, в MS-DOS файлы исполнимой программы могут быть
         в двух вариантах: файлы типа .COM и файлы типа .EXE. Рисунки 2-3
         (в главе  2)  и  3-6  иллюстрируют некоторые различия между этими
         двумя типами файлов.  Для MS-DOS различия  проявляются  в  других
         формах.
             Тип файла .EXE - это  в действительности "естественный" режим
         файла в MS-DOS. Средства языков программирования и системы MS-DOS
         предназначены для работы с этим типом  файлов.  Тип  файлов  .COM
         первоначально  был создан для совместимости с процессами операци-
         онной системы CP/M,  но этот тип не похож на вымирающий.  Даже  и
         сегодня в версии MS-DOS файлы типа .COM являются упрощенной узкой
         разновидностью файлов типа .EXE. С некоторой гибкостью файлы типа
         .EXE заменяются назначаемым по умолчанию форматом.COM. В  резуль-
         тате такого упрощения файлы типа .COM загружаются  гораздо  быст-
         рее, но различие скоростей тривиально для современных машин.
             После образования процесса макроассемблер MASM не знает  и не
         заботится о том,  чтобы знать, какой тип файла ассемблируется. Во
         время компоновки  компоновщик  LINK  обнаружит, что файлы формата
         .COM не имеют стекового сегмента,  но при этом компоновщик не бу-
         дет выражать свое неудовольствие.  Вот, когда выполняется функция

                                      - 3-24 -
         EXE2BIN для  преобразования  файлов  типа .EXE в файлы типа .COM,
         различия в файлах начинают обнаруживаться.
             Все объектные файлы,  вырабатываемые макроассемблером MASM, и
         файлы типа .EXE,  создаваемые компоновщиком LINK, могут содержать
         настраиваемые (переместимые) ссылки сегмента.  Эти файлы содержат
         таблицы,  которые включают списки, где в программе делаются явные
         ссылки на программу или кодовый сегмент по его адресу.  В связи с
         тем,  что адрес сегмента в программе будет зависеть от того,  где
         он  загружен в памяти,  когда загружается программа .EXE,  MS-DOS
         должна каким-либо образом обновить ячейки в программе,  где дела-
         ется ссылка этого сегмента,  изменяя значения для указания на те-
         кущий сегмент.  Этот процесс называется relocating  (настройкой).
         Перед  рассмотрением выполнения настройки посмотрим чем этот про-
         цесс отличается от загрузки файлов типа .COM.
             Когда EXE2BIN  выполняет  преобразование  файла  типа .EXE в
         файл типа .COM,  она просматривает файл типа .EXE для нахождения
         этих  ссылок на сегменты.  Если она находит явную ссылку на сег-
         мент в программе,  или неявную ссылку на другой не базовый  сег-
         мент,  она вырабатывает сообщение об ошибке,  указывающее на то,
         что файл не может быть преобразован. Кроме этого, EXE2BIN выпол-
         няет  проверку  того,  чтобы  программа начиналась с адреса 100h
         относительно базового сегмента. Если все эти условия удовлетворя-
         ются,  то EXE2BIN удаляет из файла всю настраиваемую информацию и
         вырабатывает файл типа .COM. Различия между этими двумя форматами
         программ кратко излагаются в таблице 3-3.

                                                         Таблица 3-3
                       Различия между форматами .COM и .EXE
         ________________________________________________________________
                                        |                |
                   Атрибуты             |    тип .COM    |   тип .EXE
         _______________________________|________________|_______________
         Количество допустимых сегментов|Только 1        |Несколько сег-
                                        |                |ментов
         _______________________________|________________|_______________
         Ссылка на сегменты             |Нет             |Ссылки допуска-
                                        |                |ются
         _______________________________|________________|_______________
         Стековый сегмент               |Не указывается  |Должен быть оп-
                                        |                |ределен
         _______________________________|________________|_______________
         Начало программного кода       |ORG в 100h      |ORG не требует-
                                        |                |ся
         _______________________________|________________|_______________
         Размер программы               |Менее 64 кбайт  |Может быть лю-
                                        |                |бого размера
         _______________________________|________________|_______________
         Адрес PSP находится            |Во всех регист- |В регистрах ES
                                        |рах             |и DS
         _______________________________|________________|_______________
         Блок начального распределения  |Вся память      |Размер может
                                        |                |быть изменен
         _______________________________|________________|_______________



                                      - 3-25 -
                             Загрузка файла типа .COM

             Начальные шаги,  предпринимаемые  при  загрузке  и выполнении
         программного файла типа .COM,  идентичны  шагам,  предпринимаемым
         при  загрузке  программного файла типа .EXE.  При установке "кон-
         текста" процесса MS-DOS сначала инициализирует блок среды,  выби-
         рая информацию либо из текущей среды системы (случай, принимаемый
         по умолчанию), или из среды, указываемой порождающим процессом.
             После установки  среды,  MS-DOS  распределяет блок памяти для
         программы.  Для программ типа .COM этот блок памяти занимает  всю
         оставшуюся память. Минимально требуемый размер равен размеру фай-
         ла программы типа.COM плюс память для PSP.  После получения блока
         памяти  MS-DOS  продолжает  строить сегмент  программого префикса
         для программы в начале блока памяти.  В этой  точке  используемый
         процесс загрузки заметно отличается от того, который используется
         с программой типа .EXE.
             Файл типа  .COM читается в память непосредственно выше PSP по
         смещению 100 (шестнадцатиричное значение) в блоке  памяти  и  без
         настройки. Все регистры сегмента инициализируются для адреса сег-
         мента PSP, указатель инструкции устанавливается в 100 (шестнадца-
         тиричное значение),  а указатель стека устанавливается в значение
         0FFFE (шестнадцатиричное значение) или ниже,  если имеется  менее
         64 Кбайт памяти, доступной процессу. (Минимальное значение указа-
         теля стека равно 0100 - шестнадцатиричное  значение).  Управление
         возвращается в процесс и программа .COM начинает выполнение.
             Некоторые программы .COM имеют неприятности при  функциониро-
         вании  из-за  минимального  стека,  обеспечиваемого MS-DOS.  Если
         программа выполняется при слишком маленьком стеке, то в результа-
         те  это  может привести к росту стека вниз в раздел программы или
         данных, что  непременно  приведет к фатальному окончанию програм-
         мы. Если программа .COM требует стек, больше минимального размера
         в 256 байтов,  то программист может  построить  свой  минимальный
         стек  памяти  в  образе  программы  путем резервирования большего
         пространства памяти в конце программы. (Запомните, что MS-DOS при
         загрузке  программы  типа .COM автоматически добавит для стека не
         менее 256 байт памяти).  Этот способ при  недостатке  памяти  для
         требуемого  стека не даст возможность MS-DOS загрузить программу.

                        Формат программного файла типа .EXE

             В отличие  от программного файла типа .COM,  который содержит
         только образ программы, файл программы типа .EXE должен содержать
         всю  необходимую информацию для настройки ссылок внутреннего сег-
         мента.  Также  в связи с тем, что в программе типа .EXE не запре-
         щено  иметь свой особый стек или особую начальную точку, програм-
         мный файл типа .EXE должен содержать информацию для загрузчика  с
         целью надлежащей инициализации программы.
             Файл программы типа .EXE состоит из трех  разделов: заголовка
         файла .EXE, таблицы настройки и образа программы. Заголовок файла
         .EXE показан в таблице 3-4.  Некоторые элементы в заголовке обес-
         печивают начальное состояние образа программы. Это: MinAlloc (ми-
         нимальное распределение), MaxAlloc (максимальное распределение) и
         начальные значения SS:SP и CS:IP.  Другие элементы: настраиваемые
         элементы и смещение таблицы настройки позволяют загрузчику  обес-
         печивать доступ к таблице настройки процесса.
             Каждый элемент в таблице настройки позволяет загрузчику раз-

                                      - 3-26 -
         решать ссылку  одного  сегмента  внутри образа программы.  Каждый
         элемент содержит указатель  длины  (сегмента  или  смещения)  для
         ссылки сегмента внутри образа загрузки. Указатель самого сегмента
         является относительным по отношению к началу образа загрузки.

                                                         Таблица 3-4
                      Заголовок программного файла типа .EXE
         ________________________________________________________________
          Шестнад-|
          цатирич-|
          ное сме-|                     Содержимое
           щение  |
         _________|______________________________________________________
             00   |Сигнатура. Маркер типа файла программы .EXE: 4D5H
                  |(шестнадцатиричное значение)
         _________|______________________________________________________
             02   |Остаток. Количество байтов на последней странице файла
                  |(размер образа загрузки модуля 512 байт)
         _________|______________________________________________________
             04   |Страницы. Количество 512-байтных страниц в файле,
                  |включая заголовок.
         _________|______________________________________________________
             06   |Элементы настройки. Количество элементов в таблице
                  |настройки.
         _________|______________________________________________________
             08   |Размер заголовка. Размер заголовка в 16-байтовых па-
                  |раграфах.
         _________|______________________________________________________
             0A   |Минимальное распределение (MinAlloc). Минимальное ко-
                  |личество параграфов памяти, требуемое после конца
                  |программы.
         _________|______________________________________________________
             0C   |Максимальное распределение (MaxAlloc). Максимальное
                  |количество параграфов памяти,  требуемое  после  конца
                  |программы.
         _________|______________________________________________________
             0E   |Стековый сегмент. Начальное значение для стекового
                  |сегмента (относительно начала образа загрузки програм-
                  |мы.
         _________|______________________________________________________
             10   |Указатель стека. Начальное значение указателя стека.
         _________|______________________________________________________
             12   |Контрольная сумма. Двоичное дополнение контрольной
                  |суммы программного файла.
         _________|______________________________________________________
             14   |Указатель инструкции. Начальное значение указателя
                  |инструкции.
         _________|______________________________________________________
             16   |Кодовый сегмент. Начальное значение кодового сегмента
                  |(относительно начала образа загрузки программы).
         _________|______________________________________________________
             18   |Смещение таблицы настройки. Относительное смещение
                  |байтов от начала программного файла в таблице настрой-
                  |ки.
         _________|______________________________________________________
             1A   |Номер перекрытия. Номер перекрытия, сгенерированный
                  |компоновщиком LINK.
         _________|______________________________________________________

                                      - 3-27 -
         программы. Во время настройки начальная ссылка сегмента, включаю-
         щая образ загрузки, обновляется для включения действительных зна-
         чений сегмента.  Этот процесс мы рассмотрим более подробно только
         после того,  как разберем один большой аспект файлов .EXE  -  на-
         чальные значения распределения.

                     Блок начального распределения памяти .EXE

             В примерах,  представленных до сих пор,  считалось само собой
         разумеющимся,  что MS-DOS при загрузке программы в память распре-
         деляет всю оставшуюся память для этой программы.  Так,  в примере
         SHOUMEM,  показанном на Рис.  3-4, для программы SHOWMEM назначен
         последний и наибольший блок памяти.  Это явление было рассмотрено
         в главе 2,  в которой для программ, приведенных в листингах 2 -12
         и 2-13, была использована функция модификации блока распределения
         памяти (функция 4Ah).  Но мы намекали и на другие способы получе-
         ния свободной памяти для программ типа .EXE.  Рис. 3-6 показывает
         программу типа .EXE, которая имеет большой блок доступной памяти,
         а  последний элемент таблицы 3-3 говорит о том,  что размер блока
         начального распределения программы .EXE может быть  изменен.  Как
         это получается?
             Заголовок файла типа .EXE содержит два элемента,  которые уп-
         равляют  точным предоставлением памяти программе при ее загрузке.
         Этими двумя элементами являются MinAlloc - минимальное  распреде-
         ление памяти (по смещению 0Ah) и MaxAlloc - максимальное  распре-
         деление памяти (по смещению 0Сh).  Элемент MinAlloc сообщает заг-
         рузчику о том,  какой объем  памяти  (в  16-байтовых  параграфах)
         должна иметь программа для выполнения,  т.е.  сколько  байтов ис-
         пользует программа на самом деле. Элемент MaxAlloc, с другой сто-
         роны,  сообщает загрузчику количество параграфов памяти,  которое
         программа требует распределить для нее.
             Компоновщик MS-DOS  обычно  устанавливает  значение  элемента
         MaxAlloc в 0FFFFh,  указывающее на то, что программа желает почти
         1 Мбайт памяти. Т.к. MS-DOS не может может иметь мегабайт памяти,
         то она выделяет программе всю оставшуюся память.  Однако, если бы
         мы  указали значение элемента MaxAlloc,  равное значению элемента
         MinAlloc,  то программа получила бы требуемую ей память, а остав-
         шаяся часть была бы доступна для распределения. Для этого имеется
         два очень простых способа.
            Языки программирования фирмы "Майкрософт", включая MASM, пос-
         тавляются с утилитой,  называемой EXEMOD. Эта утилита может быть
         использована для отображения и модификации  заголовка  программы
         типа .EXE. Рис.3-9 показывает, как необходимо выполнять использо-
         вание утилиты EXEMOD для получения дампа и затем модификации  па-
         раметра MaxAlloc. Можно удивиться, увидев, что в примере значение
         параметра MaxAlloc изменяется на значение 1,  но из  рассмотрения
         Рис.3-10 можно видеть,  как на самом деле выполняется модификация
         размера памяти,  требуемого для программы SHOWMEM, и как выполня-
         ется   освобождение   памяти.  Модифицированный  образ  программы
         SHOWMEM в памяти очень похож на образ программы типа .EXE, приве-
         денный на Рис.3-6, включая свободный блок.

               C> exemod c:\guide\examples\showmem.exe

                                      - 3-28 -
         1     Microsoft R EXE File Header Utility Version 4.02
         2     Copyright c Microsoft Corp 1985-1987. All rights reserved.

               c:\guide\examples\showmem.exe       (hex)         (dec)

         3     EXE size (bytes)                        CC5          3269
         4     Minimum Load size (bytes)               AC5          2757
         5     Overlay number                            0             0
         6     Initial CS:IP                     0093:0000
         7     Initial SS:SP                     0013:0800          2048
         8     Minimum allocation (para)                 0             0
         9     Maximum allocation (para)              FFFF         65535
         10    Header size (para)                       20            32
         11    Relocation table offset                  1E            30
         12    Relocations entries                       1             1

              C> exemod c:\guide\examples\showmem.exe /max 1

         9    Maximum allocation (para)              FFFF         65535

                       Рис.3-9. Использование утилиты EXEMOD
                         для программных файлов типа .EXE:
         1 - версия 4.02 утилиты заголовка файла типа EXE  фирмы  "Майкро-
         софт";  2  -  авторское право фирмы "Майкрософт карпорэйшн" 1985-
         1987 гг.  все права зарезервированы; 3 - размер EXE (в байтах); 4
         - минимальный размер для загрузки (в байтах);  5 - номер перекры-
         тия; 6 - начальное значение CS:IP; 7- начальное значение SS:SP; 8
         - минимальное распределение; 9 - максимальное распределение; 10 -
         размер заголовка; 11 - смещение таблицы настройки; 12 - количест-
         во настраиваемых элементов.

             Увидев, что значения MinAlloc и MaxAlloc равны нулю,  Вы уди-
         витесь.  Если это имеет место, то действительный размер минималь-
         ного  распределения для программы будет равен размеру самой прог-
         раммы,  и дополнительное пространство памяти не распределяется. 1
         SM-ShowMem,  Version  1.00  c  Copyright  1988  2  MCB Size Owner
         Command Line
             ------------------------------------------------------------
             0A01   08D7   0008   DOS
             12D9   00D3   12DA   [ SHELL ]
             13AD   0003   0000   [ available ]
             13B1   0032   12DA   [ SHELL ]
             13E4   0004   13EA   c:\bin\RETRIEVE.COM
             13E9   00A9   13EA   c:\bin\RETEIEVE.COM
             1493   000F   14A4   s:\MODE.COM
             14A3   0017   14A4   s:\MODE.COM
             14BB   0010   14CD   c:\ws2000\SWITCH.COM
             14CC   0018   14CD   c:\ws2000\SWITCH.COM
             14E5   0011   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE
             14F7   00D1   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE
             15C9   8A36   0000   [ available ]
             <<<------------- End of Memory Block List ------------->>> 3
          Рис.3-10. Пример отображения из SHOWMEM с параметром MaxAlloc,
                        равным значению параметра MinAlloc:
         1 - программа показа памяти - ShowMem,  версия  1.00,  авторское
         право 1988;  2 - блок управления памятью,  размер, владелец, ко-
         мандная строка; 3 - конец списка блоков памяти.


                                      - 3-29 -
             Таким способом  необходимо определять размер всех программных
         файлов .EXE,  и даже учитывать размер EXEMOD при создании команд-
         ных файлов.  Однако, при создании файлов .EXE имеется другой спо-
         соб управления параметром MaxAlloc - способ использования  перек-
         лючателя "/CPARMAXALLOC:nnn" (сокращенно: "/CP:nnn") компоновщика
         LINK,  где nnn - значение параметра MaxAlloc, выраженное в параг-
         рафах.  Например, программа SHOWMEM может быть образована со зна-
         чением параметра максимального распределения, равным 1, путем ис-
         пользования следующей команды:

                      C> link /cp:1 showmem,,,stdlib.lib;


                          Загрузчик процесса .EXE MS-DOS

             Теперь  нам известны все составные части, входящие в програм-
         мные файлы типа .EXE,  и можно начать рассмотрение загрузки и вы-
         полнения программ типа .EXE.Так же,как и для процессов типа .COM,
         первый шаг состоит в установке контекста процесса, начиная с бло-
         ка среды.
             После установки среды либо из системных таблиц,  либо из таб-
         лиц  владельца,  в рабочую область считывается заголовок програм-
         много файла .EXE. Используя значения MinAlloc и MaxAlloc и размер
         образа  программы  (из  размера  страницы  и  размера заголовка),
         MS-DOS определяет требуемый размер блока  памяти  и  распределяет
         его.  Если значение параметра MaxAlloc равно 0FFFFh,  то при этом
         будет распределена вся память.
             После распределения блока памяти,  в  начале  блока  процесса
         создается  PSP (сегмент  программого префикса).  PSP для программ
         типа .EXE не отличается от программ типа .COM. Затем MS-DOS чита-
         ет образ  программы в память непосредственно выше PSP,  считывает
         таблицу  настройки  и  продолжает  настраивать  образ  программы.
         Рис.3-11 показывает, как элементы в таблице настройки ссылаются к
         образу программы.  Все числа на рисунке и арифметические действия
         выполняются в шестнадцатиричной системе счисления.
             Первым шагом при настройке является вычисление адреса начала
         сегмента. Он является адресом реальной памяти, который соответс-
         твует адресу  начала образа программы в файле.  На Рис.3-11 блок
         памяти процесса размещен по адресу сегмента,  равному 1000.  PSP
         занимает 100 байтов или 10 сегментов.  Адрес начала программного
         сегмента в памяти равен тогда сегменту 1010:0000, и это есть ад-
         рес, по которому загрузчик поместит образ программы.
             После загрузки образа программы  загрузчик  должен  обновить
         или настроить каждую ссылку сегмента. Когда компоновщик LINK на-
         чинает строить образ программы, он использует предполагаемый ба-
         зовый сегмент 0000.  На самом деле, программа загружается в сег-
         мент 1010,  так что к каждой ссылке сегмента необходимо добавить
         1010. Загрузчик  находит все эти ссылки путем использования таб-
         лицы настройки, которая содержит указатель на каждую ссылку сег-
         мента в программе.
             Рис.3-11 содержит две ссылки на значения сегментов.  Просле-
         дим процесс настройки для далекого (far) вызова, размещенного по
         0003:1234. Действительная ссылка сегмента находится  в четвертом
         и пятом байтах этой инструкции по адресу 0003:1237.

                                      - 3-30 -

                     Программный файл
                        типа .EXE
                     ---------------   Начальные CS:IP    =  0000:0010
                    |Заголовок прог-|  Начальный сегмент  = +1010
                    |раммного файла |                       ----------
                     ---------------   Действительные
                                       значения           =  1010:0010
                     ---------------     |
          Добавление| Таблица наст- |    |   ОБРАЗ ПРОГРАММЫ
            адреса  |     ройки     |    |      В ПАМЯТИ
            начала  | 0003:1237 --------.|   ---------------  1000:0000
           сегмента | 0005:ABCE -------.||  | Префикс прог- |
             1010    ---------------   |||  | раммного сег- |
                                       |||  |     мента     |
         0000:0000   ---------------   |||  |---------------| 1010:0000
                    |Образ программы|  |||  |Образ программы|
                    |               |  |||  |               |
         0000:0010  | START         |  || ->| START         | 1010:0010
         0003:1234  | CALL 0005:ABCD|  | -->| CALL 1015:ABCD| 1013:1234
         0005:ABCD  | MOV  AX,0007  |   --->| MOV  AX,1017  | 1015:ABCD
         0007:0000  | Data Segment  |       | Data Segment  | 1017:0000
                     ---------------         ---------------

           Рис.3-11. Процесс настройки для загрузки программы типа .EXE

         Однако, этот  адрес  является  относительным для мнимого нулевого
         базового сегмента,  а не для действительного образа  программы  в
         памяти.  Для  нахождения  действительной ссылки сегмента в памяти
         указатель таблицы настройки сам должен быть  обновлен  с  помощью
         адреса начала сегмента. Действительная ссылка на сегмент является
         адресом 1013:1237.
             Слова, указываемые в памяти,  увеличиваются затем  на  адрес
         начала сегмента.  Вызов  far  (далекий) для сегмента 0005 теперь
         станет вызовом far для сегмента 1015 - действительное размещение
         подпрограммы.
             После завершения  настройки,  регистры ES и DS процесса уста-
         навливаются на адрес сегмента PSP,  а регистры CS:IP и SS:SP ини-
         циализируются значениями,  данными в заголовке программного файла
         типа .EXE.  Оба регистра CS и SS увеличиваются  на  адрес  начала
         сегмента  образа  программы.  Например,  на Рис.3-11 адрес начала
         (START) 0000:0010 является смещением действительного адреса нача-
         ла сегмента 1010,  для формирования действительных значений CS:IP
         1010:0010, используемых при запуске программы.

                                    Перекрытия

             Рано или поздно Вам придется писать программу,  которая явля-
         ется  слишком  большой,  чтобы  разместиться в отведенном для нее
         месте памяти.  Когда это произойдет,  одним из возможных способов
         выполнения  таких программ является создание перекрытий (оверлеев
         -overlays).  Перекрытие является разделом программы,  которому не
         нужно все время находиться в памяти. Он загружается в память тог-
         да,  когда это необходимо, но после того, как он станет ненужным,
         пространство памяти, занимаемое им, может быть использовано неко-
         торым другим перекрытием.  Остаток программы,  который  не  может
         быть помещен в перекрытие,  называется корнем (root).  Все данные
         программы должны помещаться в корень,  т.к.  данные в  перекрытии
         теряются при загрузке в него очередной части программы.  Перекры-
         тия, в конце концов, являются только читаемыми.

                                     - 3-31 -
             Перекрытия являются  очень полезными объектами, и MS-DOS под-
         держивает их достаточно эффективно.  Одним из назначений  функции
         EXEC (выполнить) является загрузка перекрытий в память. Но  перед
         рассмотрением этой опции,  необходимо отметить,  что  компоновщик
         LINK MS-DOS имеет возможность создавать перекрытия и автоматичес-
         ки управлять ими!
             Правила использования  управления перекрытиями в MS-DOS прос-
         ты. Оверлейные (перекрываемые) модули не могут содержать глобаль-
         ные  или  статические  данные,  хотя постоянные данные допустимы.
         Другое правило заключается в том,  что перекрытие может быть выз-
         вано только с помощью вызова far (далекий) либо корня,  либо дру-
         гого перекрытия. Перекрытие может вызвать корень через вызов near
         (близкий).
             Способ создания перекрытия (оверлея) очень прост:  при вызове
         команды LINK,  объектные файлы,  составляющие перекрытие,  должны
         заключаться в круглые скобки.  Это все, что для них имеется. Сле-
         дующая  командная  строка создает программный файл,  использующий
         три перекрытия:

         C> link root + (init + read) + (work) + (save + exit) ,myprog ;

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

                               Резидентные программы

             При  типовом  использовании операционная система MS-DOS пред-
         ставляет собой операционную систему с одной задачей. В любой  мо-
         мент  времени в памяти выполняется только одна программа.  Факти-
         чески же,  MS-DOS имеет возможность в  любое  время  поддерживать
         несколько программ в памяти. В действительности, в любое конкрет-
         ное время выполняется только одна программа, потому что процессор
         может выполнять в любой конкретный момент времени только одну ин-
         струкцию, но программы могут быть сконфигурированы таким образом,
         что  создается  видимость  их одновременного выполнения.  Эти не-
         сколько программ  создаются  путем  загрузки программы в память с
         помощью MS-DOS и затем возврата управления к MS-DOS  без удаления
         программы  из памяти.  Поскольку программа не покидает память при
         возврате управления операционной системе, то программа называется
         резидентной.  Первым  шагом  при выполнении резидентной программы
         является установка программы в памяти.  Одним из простейших типов
         резидентных программ являются библиотеки исполняющей системы (RTL
         - run-time library), которые будут использованы в качестве перво-
         го примера.

                      Описание библиотеки исполняющей системы

             Что такое библиотека исполняющей системы? Как известно, биб-
         лиотека представляет из себя собрание полезных подпрограмм,  ко-
         торые могут быть вызваны из программы. Большинство библиотек яв-

                                      - 3-32 -
         ляются скомпонованными (отредактированными) библиотеками, в кото-
         рых требуемые подпрограммы включаются в  программный  файл  (.EXE
         или  .COM) во время компоновки. Т.к. они являются частью програм-
         много файла, подпрограммы скомпонованной  библиотеки  загружаются
         вместе  с программой при загрузке программного файла.  RTL непос-
         редственно не компонуется с программой,  но подключается во время
         выполнения.  RTL  должна уже находиться в памяти,  или она должна
         быть занесена в память,  когда это необходимо, но, в любом случае
         RTL не является частью самого программного файла.
             RTL непосредственно не объединяется с программой,  так как же
         программа выполняет ее вызов? Программа должна каким-либо образом
         поставить в известность либо операционную  систему,  либо  RTL  о
         поддержке процесса,  с помощью которого запрашивается библиотека.
         Это может быть выполнено через вызовы, внутренние прерывания, ис-
         ключительные ситуации или прерывания,  зависящие от комплекса ап-
         паратных средств и операционной  системы.  В  среде  операционной
         системы  MS-DOS/8086 наиболее подходящим способом является способ
         оповещения через прерывание.
             Почему же используют библиотеки RTL,  если они требуют допол-
         нительных  усилий:  предварительной  загрузки,  вызова  и   т.д.?
         Во-первых,  библиотеки RTL часто используются при разработке при-
         кладных  программ, которые имеют большое количество программ, со-
         вместно использующих общие подпрограммы или для обеспечения общи-
         ми  ресурсами  всех пользователей отдельного языка программирова-
         ния.  При использовании библиотек RTL,  разработчикам  необходимо
         сохранять только одну копию библиотеки, вместо того, чтобы каждая
         программа содержала такую копию. Пока интерфейс между программами
         и  RTL остается неизменным,  подпрограммы в RTL могут обновляться
         без модификации или перекомпоновки программ, которые их вызывают.
         Поэтому  RTL может выглядеть как расширение операционной системы,
         т.к.  она обеспечивает такие средства,  которые необходимы разра-
         ботчикам,   но  которые  не  поддерживает  операционная  система.
         Во-вторых,  библиотеки RTL имеют дополнительные  преимущества  по
         уменьшению  дисковой памяти и ускорению времени загрузки програм-
         мы, т.к. RTL не загружается с каждой программой в отдельности.

               Загрузка резидентных подпрограмм из командной строки

             В MS-DOS  имеется несколько способов,  которые могут быть ис-
         пользованы для загрузки образа программы в память.  Диапазон этих
         способов  простирается  от загрузки программы из командной строки
         до подпрограмм  начальной  загрузки  нижнего  уровня,  передающих
         программный  код  из  абсолютного  места на диске в фиксированные
         ячейки памяти.  Наиболее простым способом является способ исполь-
         зования загрузчика командной строки MS-DOS,  представляющий собой
         простой запрос для выполнения программы.  Резидентные  программы,
         такие   как,  например,  RTL,  загружаются в память подобно любой
         другой программе.  Однако,  после того, как резидентная программа
         загружена и начала выполняться посредством предложения ее инициа-
         лизации,  она  завершается  использованием  специального  выхода:
         функции с кодом 31h ("сохранить процесс") или  вектора прерывания
         27h ("завершить,  но оставить резидентной"). Рекомендуемой проце-
         дурой является использование функции с кодом 31h  прерывания 21h,
         которая демонстрируется в листинге 3-4.
             Функция с кодом 31h имеет два параметра:  необязательный па-
         раметр код возврата, используемый для указания состояния при вы-

                                      - 3-33 -
         ходе из подпрограммы, и обязательный параметр, представляющий со-
         бой значение размера блока памяти в параграфах,  которое остается
         распределенным за процессом. При вызове функции MS-DOS резервиру-
         ет запрошенное количество памяти,  начиная с адреса PSP (сегмента
         программого префикса). Это происходит почти также как и при вызо-
         ве  функции "Модифицировать блок распределенной памяти" с адресом
         PSP и  требуемым  размером.  В случае функции "сохранить процесс"
         MS-DOS знает,  что блок,  размер которого должен быть модифициро-
         ван, начинается с адреса PSP, так что параметр не требуется.

              Листинг 3-4. Функция с кодом 31h - "Сохранить процесс"
         ----------------------------------------------------------------

                                  ; используемый тип .COM

         program    segment
                    ORG     0
         seg_org    equ     $
                    ORG     0100h
         start:
                    ...

                    mov     dx,(offset last_byte - seg_org + 15) shr 4
                    mov     ah,31h          ; сохранить процесс
                    int     21h             ; вызов MS-DOS
                    ...
         last_byte:
         program    ends
                    end     start
                                  ; используемый тип .EXE
                    ...

                    mov     ax,es           ; получение адреса PSP
                    mov     dx,seg end_addr ; получение адреса посл.сегм.
                    sub     dx,ax           ; получение размера прогр-мы
                    mov     ah,31h          ; сохранить процесс
                    int     21h             ; вызов MS-DOS
                    ...
         program    ends
         end_addr   segment
         end_addr   ends
                    end     start
         ----------------------------------------------------------------

             В главе 2 был представлен набор формул для вычисления размера
         программы  в  параграфах.  Эти  формулы могут быть использованы с
         функцией "Сохранить процесс" также как и с функцией  "Модифициро-
         вать блок распределенной памяти". При использовании этих формул в
         резидентных программах,  появилось соответствующее уравнение, как
         показано  в  листинге 3-4.  Заметим,  что хотя функция "Сохранить
         процесс" и не требует адреса PSP, программам типа .EXE необходимо
         сохранять  адрес  PSP при выходе с целью вычисления размера прог-
         раммы.
             Т.к. память резервируется в начале PSP,  резидентные подпрог-
         раммы не должны загружаться в верхнюю часть блока памяти  (напри-
         мер,   путем   использования   переключателя  /high  компоновщика
         MS-LINK).  Если подпрограмма загружается в верхнюю часть  памяти,
         то она станет незащищенной при завершении резидентной подпрограм-
         мы, т.к. сохраняемый блок памяти размещается в начале блока памя-
         ти.  Подпрограмма сама будет размещаться выше пространства резер-
         вируемой  памяти.  Когда  подпрограмма   станет   таким   образом
         незащищенной,  MS-DOS может загрузить на то же самое место памяти
         другую программу или нерезидентную часть файла COMMAND.COM, зати-
         рая резидентную подпрограмму.
             В любом  случае, переключатель /high компоновщика MS-LINK за-
         трагивает только программы типа .EXE.  Когда конвертирующая прог-
         рамма  EXE2BIN  для файла  .COM удалит маркер "загрузка высокая",
         MS-DOS будет загружать программу с начала PSP.
             Другим способом инстоляции резидентных программ является пре-
         рывание  "завершить и оставить резидентной" int 27h,  оставленное
         из ранних версий MS-DOS.  Способ использования прерывания int 27h
         имеет ряд недостатков,  которые сводят на нет использование этого
         способа. В отличие от функции "Сохранить процесс" (Keep Process),
         прерывание  int  27h  не требует адреса блока памяти (задаваемого
         адресом PSP),  а требует этот адрес в регистре CS.  Только  файлы
         типа  .COM имеют адрес PSP в регистре  программного сегмента, за-
         трудняя использование этой функции в  программах  типа.EXE.  (Как
         выполнить изменение регистра CS и еще выполнить программу?) Кроме
         того,  параметр размер указывается в байтах,  а не в  параграфах,
         что ограничивает размер программы, который может быть сохраненным
         до 64 Кбайт (максимальный размер программы типа .COM). Единствен-
         ным  достоинством этой функции является то,  что в качестве пара-
         метра может быть использовано без преобразования смещение послед-
         него адреса, как показано ниже:

         ...
         mov        dx,offset last_byte  ; получение количества байтов
         int        27h           ; завершение и оставить резидентной
         ...
         last_byte:
         program    ends
                    end   start

             Фирма "Майкрософт" рекомендует для всех вновь разрабатываемых
         и для всех существующих модернизированных  программ преобразовать
         это прерывание в функцию с кодом 31h. При выполнении преобразова-
         ния не забудьте модифицировать параметр размер (Size) из байтов в
         параграфы.

                Доступ к резидентным подпрограммам через прерывания

             В результате  выполнения  программы,  показанной  в листинге
         3-4, в памяти системы будет  установлена  резидентная  программа.
         После  размещения необязательно вся программа должна находиться в
         памяти.  Для включения этой программы в RTL необходимо передавать
         ей намерение и сделать ее доступной для других программ.
             RTL может содержать  любую  функцию  и  сделать  любой  вызов
         MS-DOS (например, прерывание int 21h), пока библиотека вызывается
         только текущей выполняющейся программой. Это ограничение предназ-
         начено для предотвращения неумышленного повторного вызова MS-DOS,
         который приведет к сбою системы.  Следующая программа, показанная
         в листинге 3-5, содержит пример интерфейса для RTL, который может
         поддерживать много отдельных функций  и очень похож на обработчик
         прерывания MS-DOS int 21h.

                                     - 3-35 -
             Как показано в листинге 3-5, этот пример структуры может быть
         расширен путем добавления необходимого программного кода для под-
         держки подпрограмм сравнения,  получения справок о таблицах, пре-
         образования ввода-вывода, или, даже, общей области для нескольких
         программ.  Мы попытались включить некоторые  примеры  технических
         приемов,  рассмотренных в главе 2,  такие как использование пара-
         метров стека,  отчеты об ошибках и т.д. Если эта подпрограмма ис-
         пользуется  для  поддержки большого количества функций,  то можно
         заменить модель макроса таблицей переходов, как демонстрируется в
         драйвере дискового запоминающего устройства с произвольной выбор-
         кой RDISK в главе 6.
             Библиотека MACRO, упоминаемая в программе EXRTL, содержит мо-
         дель макроса, введенного в главе 1, а также макросы dis_chr отоб-
         разить  символ)  и dis_str (отобразить строку),  представленные в
         документе "Technical Reference Manual.  @DosCall" (Справочное ру-
         ководство по техническому обслуживанию.  Вызовы DOS),  и, конечно
         же, макрос для прерывания int 21h.

                        Листинг 3-5. Пример установки  RTL
                 (подпрограмма  EXRTL - Example Run-Time Library)
         ----------------------------------------------------------------

         ;====== RTL.ASM - этот файл вырабатывает файл типа .COM ======
         V_NUM   EQU     40h            ; эта RTL использует вектор 40h
         ;
         INCLUDE STDMAC.INC             ; включение файла макробиблиотеки
         ;====== СЕКЦИЯ ПРОГРАММНОГО КОДА ==============================
         ;
         frame   STRUC      ; схема структуры стека вызывающей программы
         old_bp  dw      ?              ; запомненный указатель базы
         ret_IP  dw      ?              ; адрес возврата (IP)
         ret_CS  dw      ?              ; адрес возврата (CS)
         flags   dw      ?              ; флажки вызывающей программы
         funct   dw      ?              ; номер выполняемой функции
         frame   ENDS
         ;
         code_seg SEGMENT
                 ASSUME cs:code_seg
                 ASSUME ds:code_seg
         main    PROC    FAR
                 ORG     0
         seg_org EQU     $
                 ORG     2Ch
         env_adr LABEL   WORD           ; смещение среды в PSP
                 ORG     0100h
         start:  jmp     install
         entry:  push    bp             ; сохранение указателя базы
                 mov     bp,sp          ; получение адреса стека
                 push    ds             ; сохранение сегмента данных
                 push    ax             ; сохранение регистра
                 push    bx
                 mov     ax,cs          ; установка сегмента данных
                 mov     ds,ax
                 mov     ax,[bp].flags  ; передача флажков вызывающ.пр-мы
                 sahf                   ; в AX и в мои флажки
                 clc                    ; очистка переноса (нет ошибки)

                                      - 3-36 -
                 pushf                  ; и сохранение копии флажков
                 mov     bx,[bp].funct  ; получение кода функции
                 @Case   bl,<1,2>,<f1,f2>
                 popf                   ; получение копии флажков
                 stc                    ; установка переноса-непр.функция
                 pushf                  ; сохранение копии флажков
                 jmp     short exit
         f1:     @DisStr f1msg
                 jmp     short exit
         f2:     @DisStr f2msg
         exit:   pop     ax             ; отсылка флажков обратно в стек
                 mov     [bp].flags,ax  ;           через AX
                 pop     bx             ; восстановление регистров
                 pop     ax
                 pop     ds             ; восстановление сегмента данных
                 pop     bp             ; восстановление указателя базы
                 iret                   ; возврат из прерывания
         main    ENDP
         ;
         f1msg   db      'Function # 1 performed',CR,LF,'$'
         f2msg   db      'Function # 2 performed',CR,LF,'$'
         lst_byt:                       ; последний байт для сохранения
         ;
         ; Это программа установки.
         ;
         ; Для объяснения причин удаления блока среды смотри раздел
         ; "Биты управления памятью"
         ;
         ; Удаление блока среды - DS указывает на текущий сегмент
         ; Установка ES для указания на блок среды
         ;
         install:
                 mov     es,env_adr     ; получение адреса среды
                 mov     ah,49h      ; освобождение распределенной памяти
                 @DosCall               ; вызов MS-DOS
                 jnc     setvect        ; переход, если ошибки нет
                 @DisStr fail49         ; информация об ошибке
                 mov     ah,4Ch         ; завершение процесса
                 @DosCall               ; аварийное завершение при ошибке
         ;
         ; Установка вектора - DS указывает на текущий сегмент
         setvect:
                 mov     dx,offset entry ; получение точки входа RTL
                 mov     al,V_NUM       ; установка номера вектора
                 mov     ah,25h         ; установка вектора
                 @DosCall               ; вызов MS-DOS
         ;
         ; Завершение и оставление в памяти резидентной подпрограммы
                 mov     dx,(offset lst_byt - seg_org + 15) shr 4
                 mov     ah,31h         ; сохранить процесс
                 @DosCall               ; вызов MS-DOS
         ;
         fail49  db      'Failed to Free Environment Block',CR,LF'$'
         code_seg ENDS
                 END     start
         ----------------------------------------------------------------


                                      - 3-37 -
             Особенность подпрограммы EXRTL состоит в том,  что при выпол-
         нении функции Keep Process ("сохранить процесс") отсутствует  па-
         мять для локального стека.  Это должно было бы привести к фаталь-
         ной ошибке программы EXRTL,  потому что программный  стек  станет
         полностью незащищенным и субъектом разрушения.  Однако,  этого не
         происходит,  потому что подпрограмма EXRTL не является автономной
         программой,  а вызывается другими программами, которые имеют свои
         локальные стеки.  Подпрограмма EXRTL выполняет все свои операции,
         используя стек вызывающей программы.
             После написания RTL, необходимо обеспечить некоторые средства
         ее использования. В связи с тем, что в процессе работы невозможно
         определить,  где MS-DOS загрузит процедуру в памяти, нельзя вызы-
         вать  библиотеку  по инструкции CALL из программы,  желающей осу-
         ществить доступ к RTL.  Для этого семейство микропроцессоров 8086
         предоставляет одно решение в форме векторов прерываний. При уста-
         новке вектора прерывания  в  точке  адресации  библиотеки   любая
         программа может осуществить к ней доступ путем использования инс-
         трукции INT.
             Семейство микропроцессоров  8086  поддерживает  256  векторов
         прерываний,  из которых,  по крайней мере, 64 (от 00h до 39h) ре-
         зервируются  для аппаратуры системы или MS-DOS.  Частичный список
         векторов прерываний,  используемых  фирмой  "Интел",  стандартами
         фирмы  "ИБМ",  базовой  системой ввода-вывода (BIOS - Basic Input
         Output System) фирмы "ИБМ" и MS-DOS приведен в таблице  3-5.  Ос-
         тальные  векторы  прерываний используются другими изготовителями.
         Обычно векторы с более высокими номерами являются более надежными
         для использования, хотя это можно подтвердить только тестировани-
         ем.  В нашем случае выбран вектор 40h, потому что при его исполь-
         зовании система не разрушается.
           ____________________________________________________________
          |                                                            |
          |                      ПРЕДОСТЕРЕЖЕНИЕ                       |
          |                                                            |
          |    Многие системы могут использовать векторы прерываний,   |
          |    отличающиеся от определенных  для  MS-DOS.  Перед ис-   |
          |    пользованием  любого  вектора  проверьте  Руководство   |
          |    системы.  В  результате  изменения  уже используемого   |
          |        вектора может произойти полный отказ системы !      |
          |____________________________________________________________|



                                                         Таблица 3-5
              Векторы прерываний стандартов фирмы "ИБМ", процессора,
                         аппаратных средств, BIOS и MS-DOS
         ________________________________________________________________
                       |            |
           Прерывание  | Определено |            Использование
         (шестн.знач.) |            |
         ______________|____________|____________________________________
         Int 0         |   Интел    | Прерывание из-за ошибки деления на
                       |            | нуль
         Int 1         |   Интел    | Прерывание "прослеживания" одного
                       |            | шага
         Int 2         |   Интел    | Немаскируемое прерывание аппаратных
                       |            | средств
         Int 3         |   Интел    | Прерывание контрольной точки

                                      - 3-38 -
         ________________________________________________________________
                       |            |
           Прерывание  | Определено |            Использование
         (шестн.знач.) |            |
         ______________|____________|____________________________________
         Int 4         |   Интел    | Прерывание из-за переполнения при
                       |            | умножении
         Int 5         |   Интел    | Исключительная ситуация "граница
                       |            | 80x86"
                       |   BIOS     | Функция печати экрана
         Int 6         |   Интел    | Исключительная ситуация "неопреде-
                       |            | ленный код операции"
         Int 7         |   Интел    | Исключительная ситуация "код опера-
                       |            | ции ESC"
         Int 8 / IRQ 0 |   ИБМ      | Аппаратные средства системного тай-
                       |            | мера
         Int 9 / IRQ 1 |   ИБМ      | Аппаратные средства клавиатуры
         Int A / IRQ 2 |   ИБМ - XT | Запасной запрос аппаратных средств
         Int A / IRQ 2 |   ИБМ - AT | IRQ 8 - IRQ F
         Int B / IRQ 3 |   ИБМ      | Аппаратные средства последователь-
                       |            | ного порта 2
         Int C / IRQ 4 |   ИБМ      | Аппаратные средства последователь-
                       |            | ного порта 1
         Int D / IRQ 5 |   ИБМ - XT | Аппаратные средства фиксированного
                       |            | диска
         Int D / IRQ 5 |   ИБМ - AT | Параллельный порт 2
         Int E / IRQ 6 |   ИБМ      | Аппаратные средства дискового конт-
                       |            | роллера
         Int F / IRQ 7 |   ИБМ      | Аппаратные средства параллельного
                       |            | порта 1
         Int 10        |   BIOS     | Обслуживание видео и экрана
         Int 11        |   BIOS     | Список аппаратуры считывания
         Int 12        |   BIOS     | Размер отчета памяти
         Int 13        |   BIOS     | Обслуживание дискового ввода-вывода
         Int 14        |   BIOS     | Обслуживание последовательного
                       |            | ввода-вывода
         Int 15        |   BIOS     | Обслуживание кассетной ленты и
                       |            | расширенное обслуживание
         Int 16        |   BIOS     | Обслуживание ввода-вывода клавиату-
                       |            | ры
         Int 17        |   BIOS     | Обслуживание ввода-вывода принтера
         Int 18        |   BIOS     | Загрузчик Бэйсика
         Int 19        |   BIOS     | Программа начальной загрузки (на-
                       |            | чальный загрузчик)
         Int 1A        |   BIOS     | Обслуживание системного таймера и
                       |            | часов
         Int 1B        |   BIOS     | Клавиши Control-Break клавиатуры
                       |            | (из Int 9)
         Int 1C        |   BIOS     | Часы таймера пользователя
                       |            | (из Int 08)
         Int 1D - 1F   |   Интел    | Зарезервировано
         Int 20        |   MS-DOS   | Функция завершения старой (OLD)
                       |            | программы
         Int 21        |   MS-DOS   | Вызов функции MS-DOS
         Int 22        |   MS-DOS   | Адрес завершения программы
         Int 23        |   MS-DOS   | Адрес выхода Control-C

                                      - 3-39 -
         ________________________________________________________________
                       |            |
           Прерывание  | Определено |            Использование
         (шестн.знач.) |            |
         ______________|____________|____________________________________
         Int 24        |   MS-DOS   | Адрес аварийного завершения из-за
                       |            | фатальной ошибки
         Int 25        |   MS-DOS   | Функция чтения по абсолютному ад-
                       |            | ресу на диске
         Int 26        |   MS-DOS   | Функция записи по абсолютному ад-
                       |            | ресу на диске
         Int 27        |   MS-DOS   | Функция "завершить и оставить
                       |            | резидентной"
         Int 28        |   MS-DOS   | Цикл клавиатуры/Простой DOS (заре-
                       |            | зервировано)
         Int 29        |   MS-DOS   | Быстрый вывод консоли (зарезервиро-
                       |            | но)
         Int 2A        |   MS-DOS   | Интерфейс MS-NET (зарезервировано)
         Int 2B - 2D   |   MS-DOS   | Зарезервировано для MS-DOS (IRET)
         Int 2E        |   MS-DOS   | Команда "выполнить" (зарезервиро-
                       |            | вано)
         Int 2F        |   MS-DOS   | Управление принтером MS-DOS вер-
                       |            | сии 3
         Int 30 - 3E   |   MS-DOS   | Зарезервировано для MS-DOS
         Int 3F        |   MS-DOS   | Управление оверлейным компоновщи-
                       |            | ком
         Int 4A        |   BIOS     | Часы реального времени (от int 70)
         Int 67        |   EMS 4.0  | Спецификация расширяемой памяти
         *Int 70/IRQ 8 |   ИБМ      | Аппаратные средства часов реального
                       |            | времени
         *Int 71/IRQ 9 |   ИБМ      | Прерывания аппаратных средств IRQ 2
         *Int 72/IRQ A |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         *Int 73/IRQ B |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         *Int 74/IRQ C |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         *Int 75/IRQ D |   ИБМ      | Аппаратные средства сопроцессора
         *Int 76/IRQ E |   ИБМ      | Аппаратные средства фиксированного
                       |            | диска
         *Int 77/IRQ F |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         ______________|____________|____________________________________
         * - только шины типа AT
             В MS-DOS векторы прерывания могут быть установлены  посредс-
         твом использования функции MS-DOS с кодом 25h "Установить вектор
         прерывания". Операция установки очень проста: в регистр AL зано-
         сится номер  вектора,  а адрес для загрузки в вектор заносится в
         пару регистров DS:DX (сегмент:смещение).  В связи с тем,  что  в
         программах типа  .COM  регистр  DS устанавливается в то же самое
         значение, что и регистр CS,  содержимое регистра DS уже является
         правильным для  вызова.  Затем загружаются оставшиеся регистры и
         делается вызов с помощью следующего программного кода:
             mov       dx,offset entry    ; получение точки входа RTL
             mov       al,v_num           ; установка номера вектора
             mov       ah,25h             ; установка вектора прерывания
             doscall                      ; вызов MS-DOS

                                      - 3-40 -
             После того,  как  подпрограмма EXRTL установлена в памяти   и
         осуществляет доступ к вектору прерывания, установленному в табли-
         це векторов прерываний,  RTL готова для использования. Для ее вы-
         зова подпрограмма использует инструкцию 40h и управление  переда-
         ется  к  подпрограмме  EXRTL.  Программа  RTL_TEST,  показанная в
         листинге 3-6,  является одним из примеров подпрограммы,  осущест-
         вляющей доступ к этой отдельной RTL.
             Интерфейс между подпрограммами  EXRTL  и  RTL_TEST  полностью
         обеспечивается через стек.  Подпрограмма RTL_TEST помещает в стек
         код функции и выполняет инструкцию int 40h.  Заметим,  что  схема
         стека  в  RTL  отличается  от такого интерфейса в инструкции CALL
         (вызов),  в котором прерывание помещает флажки в  стек,  а  также
         сегмент возврата и смещение.
             Передача управления  между   двумя   секциями   показана   на
         Рис.3-12.  Инструкция  int  40h передает управление через таблицу
         векторов прерываний  в  подпрограмму  EXRTL.  Затем  подпрограмма
         EXRTL  выбирает  код  функции  из стека,  используя блок описания
         структуры стека. Подпрограмма EXRTL анализирует правильность кода
         функции и,  если он правилен, передает управление к соответствую-
         щему драйверу функции путем использования макроса case. После вы-
         полнения  функции подпрограмма EXRTL возвращает управление в под-
         программу RTL_TEST по инструкции IRET (Return  from  Interrupt  -
         возврат из прерывания).
             Стековая структура frame (смотри листинг 3-5) также обеспечи-
         вает  подпрограмме  EXRTL  доступ к флажкам вызывающей программы,
         которые хранятся в стеке  рядом  с  вектором.  Путем  копирования
         флажков  из стека в свой собственный регистр флажков подпрограмма
         EXRTL может изменить значение бита переноса;  затем,  перед выхо-
         дом,  она  может скопировать флажки обратно в стек (включая новое
         значение флажка переноса).  Эти операции  позволяют  подпрограмме
         EXRTL использовать флажок переноса для сигнализации условий ошиб-
         ки для вызывающей программы,  используя инструкцию IRET для восс-
         тановления флажков из стека.

                     Листинг 3-6. Выполнение программы для RTL
         ----------------------------------------------------------------
         ;====== RTL_TEST.ASM - Этот файл вырабатывает .COM файл ========
         V_NUM   EQU     40h          ; эта RTL использует вектор 40h
         INCLUDE STDMAC.INC           ; включение файла макробиблиотеки
         ;====== СЕКЦИЯ ПРОГРАММНОГО КОДА ===============================
         code_seg SEGMENT
                 ASSUME cs:code_seg
                 ASSUME ds:code_seg
         main    PROC    FAR
                 ORG     0100h
         start:  mov     cx,3         ; начало при неправильном значении
         loop:   push    cx           ; код функции
                 int     V_NUM        ; вызов RTL
                 pop     cx           ; очистка параметра возврата
                 jnc     nxt          ; переход, если ошибки нет
                 @DisStr caserr       ; показать ошибку
         nxt:    dec     cx
                 jge     loop         ; цикл через 0
                 mov     ah,4Ch       ; завершение процесса
                 @DosCall
         caserr  db      'Case Error - Illegal Function Code',CR,LF,'$'
         main    ENDP
         code_seg ENDS
                 END     start

                                      - 3-41 -

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

                    Адрес   .----------------------.
                            |/\/\/\/\/\/\/\/\/\/\/\| Таблица векторов
                             /\/\/\/\/\/\/\/\/\/\/\  семейства микро-
                            |                      | процессора 8086
                            |----------------------|<---------------.
                   0000:0100|     IP или CS RTL    |--------------. |
                            |----------------------|              | |
                             \/\/\/\/\/\/\/\/\/\/\/               | |
                            |\/\/\/\/\/\/\/\/\/\/\/|              | |
                            |----------------------|<-------------  |
                            |     Элемент RTL      | Установленная  |
                             \/\/\/\/\/\/\/\/\/\/\/       RTL       |
                            |\/\/\/\/\/\/\/\/\/\/\/|                |
                 .----------|         IRET         |                |
                 |          |----------------------|                |
                 |           \/\/\/\/\/\/\/\/\/\/\/                 |
                 |          |\/\/\/\/\/\/\/\/\/\/\/| Программа      |
                 |          |----------------------|пользователя    |
                 |          |/\/\/\/\/\/\/\/\/\/\/\|                |
                 |           /\/\/\/\/\/\/\/\/\/\/\                 |
                  --------->|       Int 40h        |----------------
                            |/\/\/\/\/\/\/\/\/\/\/\|
                             /\/\/\/\/\/\/\/\/\/\/\
                            |----------------------|
                            |/\/\/\/\/\/\/\/\/\/\/\|
                             /\/\/\/\/\/\/\/\/\/\/\
                            |                      |
                             ----------------------

             Рис. 3-12. Доступ к библиотеке исполняющей системы (RTL)

               Как определить, установлены ли резидентные программы?

             До сих пор мы предполагали, что библиотека исполняющей систе-
         мы (RTL) должна быть загружена в  память, и  только  после  этого
         должны стартовать программы, которые ее используют. При некоторых
         обстоятельствах RTL может всегда находиться в памяти.  Чем загру-
         жать  повторную  копию  RTL,  загрузчик сначала должен определить
         загружена ли уже RTL в память,  и загружать ее только тогда, если
         она отсутствует в памяти. Имеется два способа определения наличия
         RTL в памяти, которые оба зависят от использования предварительно
         назначенного вектора прерывания для доступа к RTL.
             Первый  способ - чтение содержимого  вектора  прерывания  по-
         средством функции  с  кодом  35h "Получить вектор прерывания" для
         определения начального адреса подпрограммы обслуживания  прерыва-
         ния  (ISR  - Interrupt service routine).  Следующий шаг состоит в
         том,  чтобы поместить в регистры DS и SI начальный адрес устанав-
         ливаемой существующей подпрограммы.  Затем выполняется инструкция
         CMPS (сравнение строк) для сравнения  некоторого  количества бай-
         тов (в регистре CS) двух секций программы. Если результат сравне-
         ния положительный, то подпрограмма уже представлена в памяти. Ес-
         ли сравнение не произошло,  то подпрограмма не была установлена в
         памяти.  Эффективность этого способа намного упадет, если все RTL
         (или резидентные  подпрограммы) будут начинаться с одинаковой по-
         следовательности инструкций.  И,  наоборот,  эффективность  может
         сильно  возрасти,  если все резидентные подпрограммы будут содер-
         жать блок заголовка,  показанный в листинге 3-7,  и который  уни-
         кально идентифицирует каждую резидентную подпрограмму.
             Второй способ проверки наличия RTL или  резидентной  подпрог-
         раммы в памяти,  требует, чтобы все неиспользуемые векторы преры-
         вания (в больших системах от вектора 40h до  вектора  0FFh)  были
         установлены в известное состояние.  Это состояние может быть либо
         верхней, либо нижней памятью (0000:0000 или FFFF:FFFF), или адре-
         сом инструкции IRET. В MS-DOS версии 2.0 и выше вектор 28h всегда
         указывает на ячейку инструкции IRET,  хотя это не  гарантируется!
         Более элегантным решением для обработки   незапрошенных  прерыва-
         ний  и  инициализации всех неиспользуемых векторов прерываний для
         указания на эту подпрограмму  (смотри  главу  6  "Устанавливаемые
         драйверы  устройств") является установка драйвера псевдо-устройс-
         тва. Этот драйвер может затем содержать инструкцию IRET, отчет об
         ошибках,  выдаваемый на консоль,  или все,  что потребуется.  При
         постоянном распределении одного вектора для  постоянного указания
         на драйвер незапрошенных прерываний (например,  вектор 40h) прог-
         рамма установки может прочитать и сравнить этот вектор  и  вектор
         резидентной подпрограммы,  чтобы убедиться в том, была ли уже ре-
         зидентная программа установлена в памяти.





               Листинг 3-7. Идентификация входных строк подпрограммы
         ----------------------------------------------------------------

         enter: jmp start                ; обход области данных
                 db '<имя подпрограммы>' ; здесь задается имя подпр-мы
                 ...     ...             ; область данных
         start:  <начало программного кода>
                 ...     ...
         ----------------------------------------------------------------

                    Удаление резидентных подпрограмм из памяти

             Когда программа закончила использование RTL,  или когда рези-
         дентная подпрограмма больше не нужна,  может возникнуть необходи-
         мость  восстановления памяти,  которая была распределена для этой
         подпрограммы.  Наиболее  простым  способом  удаления  резидентной
         подпрограммы является перезагрузка системы.  Это позволит восста-
         новить все векторы, которые требует система, и возвратить системе
         всю  распределенную память.  Однако,  это очень решительный шаг и
         лучше зарезервировать его для безнадежных ситуаций.
             Удаление подпрограммы без перезагрузки системы необходимо вы-
         полнять с помощью следующих двух шагов:
             1. Отключение подпрограммы.
             2. Восстановление памяти.
             Первый шаг  состоит в установке в нулевое состояние вектора,

                                      - 3-43 -
         указывающего на подпрограмму.  Нулевое состояние для любых потен-
         циальных  пользователей  означает,  что подпрограмма больше недо-
         ступна. Если резидентная подпрограмма была расположена на участке
         памяти  ("заплате")  для  ранее существующего вектора,  то вектор
         должен быть восстановлен так, чтобы он указывал на первоначальную
         ячейку. Можно написать программу для восстановления вектора, если
         значение  старого вектора хранится где-нибудь в резидентной  под-
         программе, и программа восстановления может найти его.  Этот про-
         цесс сохранения вектора для его последующего  восстановления  де-
         монстрируется в листингах программ INIT28 (листинг 3-12) и REMOVE
         (листинг 3-13).
             Если память резидентной подпрограммы  управляется  с  помощью
         своего  собственного прерывания аппаратных средств (но не в форме
         "заплаты"), то перед удалением резидентной подпрограммы необходи-
         мо обеспечить невозможность возникновения прерывания от этого ус-
         тройства.  Можно изменить вектор в таблице или оставить его  так,
         как он есть.

          РЕГИСТРЫ                                       ВНЕШНИЙ СЕГМЕНТ
                                                       .----------------.
         AX:4B00 (шест.)<---Функция ВЫПОЛНИТЬ ПРОГРАММУ|       ...      |
                                                       |----------------|
         BX:Указатель на имя файла в ASCIIZ ---------->| имя файла/пути |
                                                       |  нулевой байт  |
         DX:Указатель на блок параметра --------.      |----------------|
                                                |      |       ...      |
          Адрес ENVIRONMENT (СРЕДА) | \/\/\/\/\/\/\/\/ xxxx:0000 .--------
         ---------. |
                   | ASCIIZ string 1 |<----.    |
                   | ASCIIZ string 2 |     |    |
                   |       ...       |     |    |        СЕГМЕНТ ДАННЫХ
                   | ASCIIZ string N |     |    |      .----------------.
                   |  нулевой байт   |     |    |      |        ...     |
                    -----------------      |     ----->|----------------|
                                            -----------|envir.seg или 0 |
                                                       |----------------|
         Текстовый буфер командной строки <------------|DWORD:указывает |
                                                       |      текст     |
                                                       |----------------|
         File Control Block 1:load @ 5Ch  <------------|DWORD:указывает |
         (FCB1-блок управления файлом 1:загрузка @ 5Ch)| FCB 1          |
                                                       |----------------|
         File Control  Block  2:load @ 6Ch <-----------|DWORD:указывает |
         (FCB2-блок управления файлом 2:загрузка @ 6Ch)| FCB 2          |
                                                       |----------------|
         Замечание: Все указатели DWORD хранятся как,  | OFFSET  (смеще-|        Замечание: Все указатели DWORD хранятся как   | OFFSET  (смеще-
                    следующее  после SEGMENT (сег-     |  ние)          |                   ние),  следующее  после \/\/\/\/\/\/\/\/ SEGMENT (сег-
                    мента)                              \/\/\/\/\/\/\/\/                    мента)

              Рис.3-13. Блок параметров для  функции  4Bh  (AL=0)  -
                                EXECUTE (выполнить)

             После того, как резидентная подпрограмма или RTL будут отклю-
         чены, необходимо выполнить второй шаг, заключающийся в восстанов-
         лении  памяти.  Память  восстанавливается  из  MS-DOS посредством
         функции с номером 49h "Освободить распределенную  память". MS-DOS
         безразлично,  относится  или нет восстанавливаемая память к прог-

                                     - 3-44 -
         рамме,  так что если адрес начала блока памяти занят  резидентной
         подпрограммой,  то память может быть освобождена и восстановлена.
         Установленная подпрограмма может обычно  определить  этот  адрес,
         так как одной из ее опций является обеспечение кода функции вызо-
         ва подпрограммы и сообщения ей о запрещении и удалении самой  се-
         бя.  Для подпрограмм,  которые могут быть установлены посредством
         использования векторов прерываний,  для целей инструктирования об
         удалении самой себя,  может быть распределен второй вектор преры-
         вания.
             Если известно, что адрес сегмента вектора прерывания подпрог-
         раммы и адрес сегмента блока памяти  подпрограммы  одинаковы,  то
         другим способом является написание программы чтения вектора,  оп-
         ределения из него адреса сегмента блока памяти,  и инструктирова-
         ние MS-DOS об освобождении памяти.
             В некоторых случаях ни один из этих способов не работает, так
         как MS-DOS не может восстановить всю память. Проблема скорее все-
         го относится к внутренним проблемам MS-DOS, так как возникает не-
         адекватное  представление для выполнения некоторых противоречивых
         требований.

                   Функция 4Bh - загрузка и выполнение программ

             Резидентные подпрограммы и RTL очень часто инициируются с по-
         мощью  входного  файла пользователя или командного файла,  но при
         случае программе может понадобиться загрузить другую  программу в
         память,  либо  использовать ее в качестве оверлейной (перекрывае-
         мой) программы,  или как  часть  процесса  установки  резидентной
         подпрограммы. В любом случае, первоначальная программа называется
         порождающей (parent), а другая программа порожденной (child).
             MS-DOS для таких случаев обеспечивает функцию загрузки и  вы-
         полнения  программ  с кодом 4Bh.  Эта функция может выполняться в
         одном из двух режимов.  Первый режим  -  выполнение  программы  -
         предназначен  для загрузки программного файла в память и выполне-
         ния этой программы.  Порожденная программа выполняется без управ-
         ления со стороны порождающей программы. Этот режим выбирается пу-
         тем  установки  регистра  AL  в  нулевое  значение  и  установкой
         соответствующих значений в блоке параметров. Параметры, требуемые
         для  выполнения функции 4Bh,  показаны на рис.3-13,  а пример за-
         грузки  и  выполнения  программы содержится в программе LOAD (за-
         грузка), показанной в листинге 3-8.  Макробиблиотека, упоминаемая
         в программе LOAD,  является такой же,  какая использована в прог-
         рамме EXRTL (смотри листинг 3-5).

             Листинг 3-8. Загрузка программы с помощью функции MS-DOS
                                   4Bh (AL = 0)
         ----------------------------------------------------------------

         ;======LOAD.ASM - Этот файл вырабатывает файл типа .COM ========
         ; LOAD имеет возможность загрузки и выполнения другой программы.
         ; LOAD вызывается путем набора следующей информации:
         ;      "LOAD <имя файла> < аргументы программы>
         ; Между LOAD и именем файла, а также между именем файла и аргу-
         ; ментами должен быть только один символ "пробел". Имя файла
         ; должно включать расширение.
         ;
         NEWPROG EQU     82h  ;  адрес загрузки командной строки в PSP
         NEWSTR  EQU     81h  ; адрес строки в PSP (пробел 20h)
         NEWLEN  EQU     80h  ; адрес длины командной строки

                                     - 3-45 -
         ;
         INCLUDE STDMAC.INC      ; включение описания макробиблиотеки
         ;====== ПРОГРАММНАЯ СЕКЦИЯ =====================================
         ;
         code_seg SEGMENT
                 ASSUME  cs:code_seg
                 ASSUME  ds:code_seg
                 ORG     0
         SEG_ORG EQU     $
         main    PROC    FAR
         start:
                 mov     sp,offset TOP_STK ; установка вершины стека
         ;
         ; Грамматический разбор командной строки для поиска конца или
         ; пробела. Преобразование имени программы в строку ASCIIZ.
         ;
                 mov     bx,0              ; очистка BX
                 mov     bl,NEWLEN[bx]     ; получение длины ком.строки
                 or      bl,bl             ; проверка длины строки
                 jnz     cmd_ok
                 @DisStr bad_cmd           ; ошибка в командной строке
                 jmp     exit
         cmd_ok:
                 dec     bx                ; вычитание 1 для пробелов
                 mov     cx,bx             ; копирование длины в счетчик
                 mov     di,NEWPROG        ; поиск адреса (1-й не пробел)
                 mov     al,' '            ; поиск значения (пробел)
                 repne   scasb             ; поиск расширения файла
                 pushf                     ; сохранение результата поиска
                 sub     bx,cx             ; получение оставшегося счетчи-
                 popf                      ; ка и результатов поиска
                 jz      set_zb            ; нулевой флажок => параметры
                                           ; (найден пробел)
                 inc     bx                ; ненулевой флажок подразуме-
                                           ; вает конец строки
         set_zb:                           ; преобразование командной
                                           ; строки в ASCIIZ
                 mov     byte ptr NEWSTR[bx],0
                 mov     cmd_buf,cl        ; установка длины строки пара-
                                           ; метра
                 cmp     cl,0              ; достигнут конец строки?
                 jle     free_mem          ; нет параметров команды
         ;
         ; Прием остатка строки и передача его в текстовый буфер команд-
         ; ной строки для вызываемой программы
         ;
                 inc     cl                ; передача CR
                 mov     si,di             ; передача исходного индекса
                 mov     di,offset cmd_txt ; или установка индекса назна-
                                           ; чения
                 rep     movsb             ; передача остатка строки
                 add     cmd_buf,1         ; увеличение количества проб.
         ;
         ; Освобождение системной памяти для загрузчика и вызываемой
         ; программы. Сокращение блока распределения до необходимого
         ; минимума.

                                      - 3-46 -
         ;
         free_mem:
                 mov     bx,(offset LST_BYT - SEG_ORG + 15) shr 4
                 mov     ah,04Ah           ; ES содержит адрес PSP
                 @DosCall                  ; модификация распред. памяти
                 jnc     modify_ok
                 push    ax                ; (помещение в стек при ошиб-
                                           ; ке)
                 @DisStr fail4A            ; сообщение об ошибке или
                                           ; завершение, если сбой
                 jmp     error
         ;
         ; Установка блока параметров и регистровых параметров для
         ; вызова функции загрузки или выполнения программы.
         modify_ok:
                 mov     ax,cs             ; установка всех параметров
                 mov     p1,ax             ; сегментов в этот сегмент
                 mov     p2,ax
                 mov     p3,ax
                 mov     dx,offset NEWPROG
                 mov     bx,offset param_block
                 mov     spoint,sp         ; сохранение указателя стека
                 mov     ax,4B00h          ; функция загрузки или выпол-
                                           ; нения программы
                 @DosCall
         ;
         ; Восстановление регистров сегмента и указателя стека после
         ; вызова.
         ;
                 mov     cx,cs             ; дублирование CS во все сегм.
                 mov     ss,cx          ; сначала восстанавливается стек
                 mov     sp,cs:spoint   ; восстановление указателя стека
                 mov     ds,cx
                 mov     es,cx
                 jnc     exit      ; выход из программы, если все хорошо
                 push    ax                ; сохранение кода ошибки
                 @DisStr fail4B            ; вывод ошибки, если сбой
         ;
         ; Грамматический разбор кода ошибки, возвращаемого системой
         ; и отображение соответствующего текста сообщения
         ;
         error:
                 pop     ax        ; обратное получение кода ошибки
         @Case   ax,<+,2,7,8,9,10h,11h>,<em1,em2,em7,em8,em9,em10,em11>
                 mov     dx,offset err0    ; плохой код ошибки - неравны
                 jmp     merge
         em1:    mov     dx,offset err1    ; неправильная функция
                 jmp     merge
         em2:    mov     dx,offset err2    ; файл не найден
                 jmp     merge
         em7:    mov     dx,offset err7    ; память засорена
                 jmp     merge
         em8:    mov     dx,offset err8    ; недостаточно памяти
                 jmp     merge
         em9:    mov     dx,offset err9    ; неправильный блок памяти
                 jmp     merge
         em10:   mov     dx,offset err10   ; неправильная среда

                                      - 3-47 -
                 jmp     merge
         em11:   mov     dx,offset err11 ; неправильный формат файла .EXE
                 jmp     merge
         merge:  mov     ah,09h            ; отображение строки
                 @DosCall
         exit:   mov     ax,04C00h         ; завершение при окончании
                 @DosCall
         main    ENDP
         ;
         bad_cmd db      'Error in Command Line',CR,LF,'$'
         ; ошибка в командной строке
         fail4A  db      'Failed to Modify Allocated Memory Blocks'
                 db      CR,LF,'$'
         ; сбой при модификации блока распределенной памяти
         fail4B  db      'Failed to Load Program Overlay',CR,LF,'$'
         ; сбой при загрузке программного оверлея
         err0    db      '>>> UNKNOWN ERROR CODE <<<',CR,LF,'$'
         ; неизвестный код ошибки
         err1    db      '>>> invalid function <<<',CR,LF,'$'
         ; неправильная функция
         err2    db      '>>> file not found <<<',CR,LF,'$'
         ; файл не найден
         err7    db      '>>> memory arena trashed <<<',CR,LF,'$'
         ; память засорена
         err8    db      '>>> not enough memory <<<',CR,LF,'$'
         ; недостаточно памяти
         err9    db      '>>> invalid memory block <<<',CR,LF,'$'
         ; неправильный блок памяти
         err10   db      '>>> bad environment <<<',CR,LF,'$'
         ; неправильная среда
         err11   db      '>>> bad .EXE file format <<<',CR,LF,'$'
         ; неправильный формат файла .EXE
         ;
         spoint  dw      ?                 ; память для указателя стека
         param_block     label word
                 dw      0    ; используемая среда порождающей программы
                 dw      offset cmd_buf
         p1      dw      ?                 ; сегмент командной строки
                 dw      5Ch               ; сегмент FCB #1 или смещение
         p2      dw      ?
                 dw      6Ch               ; сегмент FCB #2 или смещение
         p3      dw      ?
         cmd_buf db      ?                 ; длина командной строки
                 db      ' '               ; всегда предполагается пробел
         cmd_txt db      80 dup (?)        ; 80 символов
         ;
         ; Описание локального стека
         EVEN                              ; слово выравнивания стека
         stack   db      32 dup ('stack   ') ; локальный стек
         TOP_STK EQU     $-2           ; установка адреса вершины стека
         LST_BYT EQU     $                 ; последний байт в программе
         ;
         code_seg ENDS
                 END     start
         ----------------------------------------------------------------

             Второй режим  называется  Load  Overlay (загрузка оверлейных

                                      - 3-48 -
         программ). Хотя в этом режиме загружается программный файл, режим
         Load Overlay не выполняет вызов программы.  Вместо этого управле-
         ние сразу же возвращается в вызывающую программу.  Этот режим вы-
         бирается путем установки в регистре AL значения,  равного 3. Блок
         параметров для этого режима показан на Рис. 3-14.
             В любом режиме  функционирования  перед  выполнением  функции
         загрузки и выполнения программы блок начального распределения вы-
         зывающей программы должен быть приведен в исходное  состояние для
         освобождения пространства памяти. MS-DOS загружает программы, ис-
         пользуя программный загрузчик файла COMMAND.COM, который не явля-
         ется резидентной частью файла COMMAND.COM.  Программный загрузчик
         должен считать в память сам себя с диска перед тем, как он сможет
         загрузить  программу пользователя или оверлейную программу.  (Это
         также предполагает,  что в системе для этой функции должен  рабо-
         тать диск, содержащий файл COMMAND.COM).

             РЕГИСТРЫ                                    ВНЕШНИЙ СЕГМЕНТ
                                                       .----------------.
         AX:4B03(шестн.) <------ Функция Load Overlay  |       ...      |
                                                       |----------------|
         BX:указатель на имя файла в коде ASCIIZ ----->| имя файла/пути |
                                                       |  нулевой байт  |
         DX: указатель на блок параметров ------.      |----------------|
                                                |      |       ...      |
                                                |       \/\/\/\/\/\/\/\/
                                                |
         Адрес       ПАМЯТЬ СИСТЕМЫ             |        СЕГМЕНТ ДАННЫХ
         0000:0000 .----------------.           |      .----------------.
                   |Система или по- |           |      |                |
                   |рождающая прог- |           |      |       ...      |
                   |    рамма       |           |      |                |
                   |      ...       |            ----->|----------------|
                   |                |                  |Адрес загрузоч- |
         xxxx:0000 |----------------|<-----------------|ного сегмента   |
                   | Программный код|                  |----------------|
                   |      ...       |                  |Фактор настройки|
                   |----------------|                  |----------------|
                   |                |                  |                |
                    \/\/\/\/\/\/\/\/                    \/\/\/\/\/\/\/\/

               Рис.3-14. Блок параметров для функции 4Bh (al = 3) -
                   LOAD OVERLAY (Загрузка оверлейной программы)

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

           Загрузка и выполнение программ через MS-DOS (код 4Bh с AL=0)

             При использовании функции загрузки и  выполнения MS-DOS  тре-
         бует  не  только достаточного количества свободной памяти для за-
         грузки  программного  загрузчика  файла  COMMAND.COM,   но  также
         достаточное  количество  свободной  памяти  для  размещения новой

                                     - 3-49 -
         программы. Эта память используется также для создания  блока  на-
         чального распределения новой программы.  Вспомните,  что блок на-
         чального распределения порождающей программы должен быть установ-
         лен  достаточно большим для предохранения текущей программы,  или
         перезаписи блока операционной системой MS-DOS при  загрузке новой
         программы.  Кроме  того,  большинство резидентных подпрограмм или
         RTL написаны в формате типа .COM. Для программ типа .COM операци-
         онная система MS-DOS устанавливает стек в начало наивысшего адре-
         са доступной памяти в общем сегменте,  который  используется  для
         программного кода, данных и стека. Если вершина стека не настраи-
         вается внизу сегмента,  то может быть защищено до 64 Кбайт порож-
         дающей программы.  Если же стек настраивается внизу сегмента,  то
         все, что находится в стеке, будет потеряно (например, при возвра-
         те в MS-DOS).  Конечно,  возврат в MS-DOS в стеке не нужен,  если
         используется функция с кодом 4Ch.

                  Наследство и управление порожденной программой

             Даже если порожденная программа автономна,  порождающая прог-
         рамма  все же  имеет степень воздействия на поведение порожденной
         программы.  Это воздействие выполняется  через  наследство,  т.е.
         возможность порождающего процесса воздействовать каким-либо обра-
         зом на связь порожденного процесса с остальной системой.
             Из Рис. 3-13  можно видеть, что порождающий процесс применяет
         порожденный процесс с командной строкой, блоком среды (или с бло-
         ком  порождающего процесса,  если блок не указан в вызове EXEC) и
         блоками управления файлами.  Кроме того, когда процесс загружает-
         ся,  он автоматически наследует большинство сегментов программого
         префикса своей программы,  включая таблицу описателей файлов  по-
         рождающей программы.  При манипулировании этими элементами порож-
         дающая программа управляет тремя первичными элементами, управляю-
         щими  программой:  ее  командной строкой,  ее таблицей описателей
         файлов и ее блоком среды.
             Между командными  строками:  передаваемой  в порожденный про-
         цесс, и используемой в приглашениях системы имеются некоторые от-
         личия.  В первом случае командная строка становится ответственной
         порождающего процесса за установку любого переназначения -  зада-
         чи,  обычно обрабатываемой файлом COMMAND.COM.  Т.к.  порожденный
         процесс наследует таблицу описателей файлов порождающего его про-
         цесса,  то порождающий процесс может легко переназначить ввод/вы-
         вод порожденного им процесса.  При изменении значений описателей,
         хранящихся  в  устройствах  стандартного  ввода  и вывода stdin и
         stdout порождающего процесса,  порождающий процесс изменит  пред-
         шествующие как stdin,  stdout, так и любые другие допустимые уст-
         ройства порожденного процесса. Порождающий процесс может изменить
         их,  используя  технические приемы,  показанные в листинге 3-3 (в
         разделе "Таблица описателей файлов PSP"), или путем использования
         функций MS-DOS для манипулирования файлами и устройствами. (Одной
         из таких функций,  которая может быть использована для замены об-
         работки, является функция MS-DOS с кодом 46h "Использовать дубли-
         кат описателя"- прерывание int 21h).
             Заметим, что полная обработка  может  быть  включена  из  су-
         щества  наследования.  При  открытии  файла или устройства должен
         быть  указан  режим OPEN (открыть) (Смотри функцию MS-DOS с кодом
         3Dh "Открыть файл или устройство" - прерывание int 21h), при этом
         бит 7 режима OPEN является битом наследования. При установке это-
         го  бита в 0  (принимается по умолчанию), обработка   должна быть

                                     - 3-50 -
         наследуемой с каким-либо порожденным процессом. Если этот бит ус-
         тановлен в 1 при вызове функции OPEN, то возвращаемая  обработка
         будет освобождена от наследования.
             Существует другой способ, при котором порождающий процесс мо-
         жет управлять системным отображением порожденного процесса.  Пер-
         вый элемент в блоке параметров  загрузки  и  выполнения  является
         указателем на блок среды порожденного процесса.  Если указатель в
         блоке параметров загрузки и выполнения равен нулю,  то для порож-
         денного процесса дублируется среда порождающего процесса. Если он
         не равен нулю, то в качестве среды порожденного процесса загружа-
         ется блок среды, указываемый этим указателем.
             Что же это означает для Вас? Это означает, что можно написать
         программу поиска блоков среды для отдельных элементов и затем ис-
         пользовать эти значения для установки параметров во время  выпол-
         нения программы.  Элементы могут быть вставлены в блок среды сис-
         темы  по  команде  SET  (установить)  для  управления  действиями
         программы,  которая читает и действует со своим блоком среды. Так
         как порождающий процесс может изменять блок среды,  то тем  самым
         порождающий процесс может изменять поведение порожденного процес-
         са, читающего этот блок.
             Выполняющийся процесс может осуществлять доступ к своему бло-
         ку среды посредством указателя,  хранящегося по  смещению  2Сh  в
         PSP.  Указатель используется в качестве адреса сегмента с нулевым
         смещением, указывающим на начало блока. Если этот адрес передает-
         ся  в регистре внешнего сегмента или в регистре данных,  то прог-
         рамма может выполнить поиск строки для нахождения требуемых пара-
         метров.  При  этом  будьте  осторожны,  потому что можно потерять
         адрес PSP.
             Информация, содержащаяся в PSP,  действительна как для файлов
         типа .COM,  так и для файлов типа .EXE,  и любой тип файла  может
         быть использован с функцией загрузки и выполнения программы.

                      Выполнение команд MS-DOS с функцией 4Bh

             Одним из  приложений  функции  загрузки и выполнения является
         загрузка файла COMMAND.COM.  Если принять во внимание,  что  файл
         COMMAND.COM может выдавать команды через командную строку тексто-
         вого буфера, то можно увидеть, что из программы пользователя мож-
         но  вызывать  встроенные  команды MS-DOS.  Кроме того,  командная
         строка, передаваемая файлом COMMAND.COM, может содержать переназ-
         начения,  каналы и фильтры.  Формат текста команды,  используемый
         при этом способе,  почти такой же, какой используется в начальной
         командной  строке,  за  исключением  того,  что  при вызове файла
         COMMAND.COM из программы,  текст команды должен начинаться с сим-
         волов /c.
             Загрузка двух программных файлов  (COMMAND.COM  и  прикладной
         программы) для выполнения только одного программного файла не яв-
         ляется высокоэффективным способом  выполнения  программ.  Однако,
         следует  принять  во  внимание  большую гибкость и производитель-
         ность, достигаемые при использовании этого способа.

                               Важное предупреждение

             Механизм функции загрузки и выполнения программ в  версии 2.0
         MS-DOS  имеет  серьезный  технический недостаток.  При выполнении
         функции "захламляются" все регистры сегментов (за исключением ко-
         дового сегмента), разрушается указатель стека и разрушается боль-

                                     - 3-51 -
         шинство общих регистров.  Если эта функция используется  с  любой
         подверсией MS-DOS версии 2.0 (т.е. 2.00 или 2.10), то перед вызо-
         вом функции необходимо сохранять в памяти указатель стека и любые
         общие  регистры,  необходимые  для  дальнейшего использования;  и
         восстанавливать из памяти регистры сегментов,  указатель стека  и
         необходимые  общие регистры после выполнения функции.  Пример по-
         следовательности программных кодов,  предназначенный для выполне-
         ния  этих  действий  для программного файла типа .COM,  показан в
         листинге 3-9.
             Для программных  файлов типа .EXE можно восстановить надлежа-
         щие значения сегмента из  значений,  установленных  компоновщиком
         LINK (например,  mov ss,stack), или из памяти, размещаемой внутри
         программного сегмента. Для защиты стека необходимо помнить о том,
         что последовательность восстановления стекового сегмента и указа-
         теля стека должна быть такой,  что сначала указывается  указатель
         стека, а затем стековый сегмент.
             Начиная с версии MS-DOS 3.0,  этот недостаток устранен. Функ-
         ция загрузки и выполнения программы возвращает все регистры "нет-
         ронутыми".

         Листинг 3-9. Восстановление необходимых элементов при выполнении
                      функции загрузки и выполнения программы для прог-
                      раммного файла типа .COM в MS-DOS версии 2.XX
        -----------------------------------------------------------------
             ...
             <установка параметров вызывающей программой>
             ...
             mov    spoint,sp   ; сохранение указателя стека в памяти
             mov    ax,4B00h    ; функция загрузки и выполнения пр-мы
             int    21h         ; вызов MS-DOS
         ; регистры не изменятся, если произойдет сбой при загрузке --
         ; восстановление не выполнять
             jc     error       ; переход при ошибке
             mov    ax,cs       ; получение общего сегмента
             mov    ds,cx       ; для сегмента данных
             mov    es,cx       ; для внешнего сегмента
             mov    ss,ax       ; и для стекового сегмента
             mov    sp,spoint   ; стек теперь повторно выровнен
             ...
             <восстановление общих регистров>
             ...
         ----------------------------------------------------------------


           Загрузка программного оверлея (перекрытия) посредством MS-DOS
                            (код функции 4Bh с AL = 3)


             Возможность выполнения  одной программы из другой является на
         самом деле огромным достижением,  но при этом имеет место сущест-
         венный недостаток,  заключающийся в том, что после выполнения вы-
         зываемой программы происходит ее завершение.  Однако,  во  многих
         случаях разработчики программ хотят вызывать другую программу для
         выполнения некоторых функций,  но при  этом  дополнительно  хотят
         иметь  большую  степень  управления  порожденной программой,  или
         большую степень связи с порожденной программой,  или даже возмож-
         ность  неоднократного вызова порождаемой программы без ее повтор-
         ной перезагрузки. Для этих случаев для функции 4Bh MS-DOS предос-
         тавляет опцию Load Overlay (загрузка оверлея).

                                     - 3-52 -
             Одним из отличий функции загрузки оверлея от функции загрузки
         и  выполнения программы является то,  что при загрузке оверлейной
         программы порождающая программа не предназначена  для модификации
         параметров порождаемой программы. Это имеет место потому, что по-
         рождающая и порождаемая программы, на самом деле, являются частя-
         ми одной и той же программы.  Все, что выполняет функция загрузки
         программного оверлея,  это загрузка дополнительного  программного
         кода (и/или данных программы) в память.
             Другим отличием загрузки оверлея от загрузки и выполнения яв-
         ляется  то,  что загрузка оверлея не требует блок памяти его вла-
         дельца.  Загрузка оверлея не передает блок среды или блок началь-
         ного распределения,  как функция загрузки и выполнения программы.
         Функция загрузки оверлея просто загружает  запрашиваемый  файл  в
         память,  настраивая значения сегментов программы в соответствии с
         параметрами,  обеспечиваемыми при вызове функции (как показано на
         Рис.3-14).  Полученный  программный  код  может быть выполнен как
         подпрограмма, но не должен выполняться как отдельная программа.
             Если оверлей  завершается посредством одной из функций завер-
         шения MS-DOS,  то завершается и оверлей и порождающая  программа.
         Если для выхода используется функция 31h или функция 27h ("завер-
         шить и оставить резидентной"),  то модифицируется блок начального
         распределения  порождающей подпрограммы,  а порождающая программа
         остается в памяти.  Порожденная программа  останется  резидентной
         только  тогда,  когда блок запрошенной памяти является достаточно
         большим,  вмещающим и порождающую и порожденную  программы.  Если
         выполняется одна из функций завершения программ, то обе программы
         удаляются из памяти.
             Рис.3-14 показывает,  что  фактор настройки,  указываемый как
         часть функции загрузки оверлея, не воздействует на адрес загрузки
         оверлея. Вместо фактора настройки используется модификация смеще-
         ния ссылок внутри загружаемой программы. Если оверлей загружается
         в формате .COM,  то фактор настройки не воздействует на загружае-
         мый оверлей и должен быть установлен в нулевое значение.
             Для программных файлов типа .EXE фактор настройки добавляется
         к значениям ссылок сегментов, которые появились в файле загрузки.
         При  загрузке  большинства оверлейных программ типа .EXE (которые
         обычно первоначально по  умолчанию  0000:0000)  фактор  настройки
         должен  быть установлен в то же самое значение,  что и адрес заг-
         рузки.

              Доступ к программному оверлею из порождающей программы

             После загрузки программного  оверлея   порождающая  программа
         должна  осуществить к нему доступ.  Так как порождающая программа
         знает адрес, по которому был загружен программный оверлей, то для
         доступа к оверлею она может выдать инструкцию CALL или инструкцию
         перехода JMP.  Обращаться к оверлею по инструкции CALL рекоменду-
         ется по той причине,  что оверлей затем сможет возвратиться в по-
         рождающую программу путем использования инструкции RET, чем запо-
         минать адрес возврата в порождающую программу для инструкции JMP.
         Если же возвращать управление в порождающую программу  не  нужно,
         то предпочтительнее использование инструкции JMP.  Оверлей, в та-
         ком случае, содержит вызов функции завершения программы.
             Все способы доступа к оверлею (либо по инструкции  CALL, либо
         по  инструкции JMP) должны быть ссылками  far (далекий). Програм-
         мный код, загружаемый в оверлей, является  относительным по отно-
         шению к собственному адресу сегмента и не  может  располагаться в

                                     - 3-53 -
         том же самом сегменте, что и порождающая программа (хотя он может
         быть загружен в тот же самый участок памяти).  Кроме того,  с по-
         мощью функции  "загрузить оверлей" не строится блок PSP.  Так как
         отсутствует  дополнительная информация,  помещаемая в память  за-
         грузчиком, программные  коды и данные из оверлейного файла загру-
         жаются, начиная точно с указанного адреса загрузки.
             Рассмотрим простейший случай : оверлей, загружаемый из файлов
         формата .COM. Все файлы формата .COM имеют начало 100 (шестнадца-
         тиричное значение). Т.е. коды этих файлов начинаются с адреса 100
         относительно их сегментов.  Все ссылки, содержащиеся в программе,
         являются относительными к этому адресу. Т.к. файл .COM загружает-
         ся прямо по адресу загрузки, можно некорректно использовать адрес
         загрузки в качестве значения сегмента для оверлея. Рис.3-15 пока-
         зывает,  что если адрес загрузки используется в  качестве  адреса
         сегмента,  то  значения  смещений в программном коде смещаются на
         100 (шестнадцатиричное значение).  Правильный адрес сегмента  для
         использования  есть  адрес  загрузки  минус 10 (шестнадцатиричное
         значение),  который перемещает смещения программного кода на  100
         (шестнадцатиричное значение).
             Для программных файлов формата .EXE существуют другие пробле-
         мы.  Когда  файл формата .EXE загружается для выполнения,  MS-DOS
         инициализирует программный и стековый сегменты  для  указания  на
         надлежащий  сегмент и указатель инструкции для указания на первую
         инструкцию программы.  Когда файл формата  .EXE  загружается  как
         оверлей, MS-DOS не обеспечивает эти значения. Как тогда порождаю-
         щая программа узнает, куда вводить программу?

                              /\/\/\/\/\/\/\/\/\/\/\/\
                             | Младшие адреса памяти |    АДРЕС СЕГМЕНТА
                             |-----------------------|<---    ОВЕРЛЕЯ
                             |            ^          |   Segment:CS_RUN
                             |            |          |   CS_LOAD:10(hex)
                             |         100 (hex)     |
                             |            |          |
          АДРЕС ЗАГРУЗКИ     |            V          |    АДРЕСА ПАМЯТИ
         Segment:CS_LOAD---->|-----------------------|<---
                             |Программный код оверлея| CS_LOAD:0000(hex)
                             |          ...          | CS_RUN: 0100(hex)
                             |-----------------------|
                             |     Данные оверлея    |
                             |          ...          |
                             |-----------------------|
                             | Старшие адреса памяти |
                             |/\/\/\/\/\/\/\/\/\/\/\/

              Рис.3-15. Взаимосвязь адреса сегмента и адреса загрузки
                                для оверлея формата .COM

             Так как  файлы  формата .EXE имеют нулевое начало,  то какую
         инструкцию CALL или JMP использовать для  перехода к адресу заг-
         рузки? Это будет зависеть от того,  как написана программа.  Для
         файлов типа .EXE, созданных из одного исходного файла, компонов-
         щик LINK и MS-DOS загружают сегменты в память в том же самом по-

                                      - 3-54 -
         рядке, в котором они появляются в исходной программе!  Общим  по-
         рядком  для  определения  сегментов является следующий:  стековый
         сегмент,  затем сегмент данных,  затем программный  сегмент.  (По
         причине  минимизации  ссылок вперед в программном сегменте).  Для
         возможности вызова программ формата .EXE по  инструкции  CALL  по
         адресу  их загрузки,  программный сегмент должен быть первым сег-
         ментом в файле .ASM, а точка входа должна быть первой инструкцией
         в программном сегменте. Макроассемблер MASM и компоновщик LINK не
         имеют с этим никаких проблем,  хотя в некоторых случаях для  MASM
         может  появиться  необходимость  использования директив замещения
         для разрешения ссылок вперед.
             Листинг 3-10  показывает как должна появляться последователь-
         ность загрузки и  вызова,  когда  используется  функция  загрузки
         оверлея для файлов формата .COM.  Последовательность для программ
         формата .EXE проще. Не нужно перемещение от адреса загрузки к ад-
         ресам при выполнении. Мы предположили, что все регистры сегментов
         в порождающей программе уже инициализированы и что уже была  выз-
         вана  функция  модификации  распределения памяти для освобождения
         достаточного пространства для загрузчика файла COMMAND.COM.

               Листинг 3-10. Загрузка и доступ к программе типа .COM
                          с помощью функции 4Bh (AL = 3)
         -----------------------------------------------------------------

                 ...                               ...
         ; Распределение памяти для оверлея
                 mov   ah,48h         ; функция распределения памяти
                 mov   bx,1000h       ; предполагается сегмент в 64 кбайт
                 int   21h            ; вызов MS-DOS
                 jc    error          ; переход, если произошла ошибка
                 mov   params,ax      ; сохранение адреса памяти
         ; Загрузка оверлея
                 mov   dx,offset params ; доступ к блоку параметров
                 mov   bx,offset filename ; доступ к имени файла в ASCIIZ
                 mov   ax,4B03h       ; функция загрузки оверлея
                 int   21h            ; вызов MS-DOS
                 jc    error          ; переход, если произошла ошибка
         ; Вызов оверлея
                 mov   ax,params      ; получение адреса загрузки
                 sub   ax,10h         ; трансляция для адреса выполнения
                 mov   run_seg,ax     ; и сохранение его
                 push  ds             ; сохранение сегмента данных
                 call  dword ptr run_adr ; вызов оверлея
         ; Освобождение памяти, которая использована для оверлея
                 pop   ds             ; восстановление сегмента данных
                 mov   ah,49h         ; функция освобождения памяти
                 mov   as,params      ; получение адреса блока памяти
                 int   21h            ; вызов MS-DOS
                 jc    error          ; переход, если произошла ошибка
                 ...                               ...
         params  dw    ?              ; адрес загрузки
                 dw    0              ; значение настройки
         run_adr dw    0100h          ; новый указатель инструкции
         run_seg dw    ?              ; новое значение кодового сегмента
         -----------------------------------------------------------------

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



                           Загрузка резидентных программ

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

             Специальный случай: библиотеки исполняющей системы (RTL)
                            с неполным временем работы

             Одной из многих возможностей, которые могут быть реализованы
         с представленными функциями, является неполное время работы биб-
         лиотеки  исполняющей системы (RTL).  Программы неполного времени
         работы RTL являются резидентными только тогда, когда это необхо-
         димо,  а остальное время располагаются на диске. Эта возможность
         реализуется  путем  установки  части заголовков RTL точно также,
         как описывалось в этой главе. Однако, эти  заголовки не содержат

                                      - 3-56 -
         программных кодов для выполнения функций библиотеки; т.е. они  не
         содержат подпрограммы своей библиотеки, которые остались на диске
         в другом файле. Последовательность событий, происходящих при  вы-
         полнении   программ   неполного   времени работы RTL,  показана в
         блок-схеме 3-1.
                   ____________________
                  |      Загрузка      |
                  |     заголовков     |
                  |____________________|
                             |
            .--------------->|<---------------------------.
            |      __________V_________                   |
            |     |                    |                  |
            |     |    Прием запроса   |                  |
            |     |____________________|                  |
            |                |                            |
            |                |                            |
            |               / \                           |
            |              /Ко-\                          |
            |             /манда\               __________|__________
            |            /освобо-\  ДА         | Освобождение распре-|
            |           | ждения  |----------->|   деленной памяти   |
            |            \памяти /             |_____________________|
            |             \RTL ?/
            |              \   /
            |               \ /
            |            НЕТ |
            |                V
            |               / \
            |              /   \
            |             /     \                ____________________
            |            /  RTL  \  НЕТ         |Распределение памяти|
            |           |установ- |------------>|  для загрузки RTL  |
            |            \ лена ?/              |____________________|
            |             \     /                          |
            |              \   /                           |
            |               \ /                            |
            |                |                             |
            |             ДА |<----------------------------
            |      __________V_________
            |     |                    |
             -----| Функция выполнения |
                  |____________________|

            Блок-схема 3-1. Последовательность загрузки RTL с неполным
                                  временем работы

             Когда одна из подпрограмм библиотеки доступна (через прерыва-
         ние), то часть заголовков подпрограмм загружает файл библиотеки в
         память, используя функцию 4Bh (AL=3) "Загрузить оверлей" и "запи-
         рает" его в своей собственной памяти.  Затем вызывается требуемая
         подпрограмма библиотеки для выполнения запрашиваемой функции. Ли-
         бо часть заголовков,  либо конкретные подпрограммы библиотеки мо-
         гут  содержать инструкцию IRET для возврата в вызывающую програм-
         му. С этого момента библиотеке доступен вызов всей последователь-
         ности  без  ожидания времени на загрузку,  поскольку RTL осталась
         резидентной в памяти.
             Когда главная  программа завершается или требует пространство
         памяти RTL,  она передает в точку входа RTL код для  освобождения
         памяти,  распределенной для RTL. Поскольку часть заголовков знает
         адрес загрузки подпрограмм библиотеки после их загрузки,  и  пос-
         
                                     - 3-57 -
         кольку  их  блоком памяти владеет часть заголовков,  освобождение
         памяти не вызывает проблем. После этого выполняется перевод части
         заголовков обратно в положение "спячки" для ожидания последующего
         вызова.

                    Переключение контекста и переключение стека

             В связи с тем, что большинство тем, обсужденных в этой главе,
         относятся  к  операциям между отдельными программами с отдельными
         стеками,  процесс переключения заслуживает  некоторого  внимания.
         Переключение стека, или переход от одного стека к другому являет-
         ся  частью  большой  темы,  называемой  переключением   контекста
         (context switching).
             Если отобразить сегменты,  в которых  выполняется  программа,
         как ее контекст, то можно увидеть, что при многих обстоятельствах
         бывает необходимо изменять полный контекст  программы.  Примерами
         этого  могут  служить вызовы резидентных подпрограмм,  вызывающих
         библиотеки RTL и использующие некоторые типы оверлеев или сопрог-
         рамм. (Сопрограмма является структурной единицей программы, кото-
         рая используется для описания логически параллельных  действий  и
         вызывается подобно подпрограмме.  В отличие от подпрограммы, каж-
         дый вызов сопрограммы возобновляет ее выполнение с точки  послед-
         него  возврата.  Сопрограмма  представляет собой вид специального
         оверлея, который не имеет связей порождающая-порожденная подпрог-
         раммы).  В этом случае, когда одна подпрограмма получает управле-
         ние,  она желает установить для выполнения свои собственные  сег-
         мент данных,  внешний сегмент и сегмент стека. Во время получения
         управления из другой программы наверняка известно только  то, что
         ее  программный сегмент и указатель инструкции установлены в над-
         лежащие значения.  Обратимся к листингу 3-9. После вызова функции
         загрузки и выполнения программы контекст вызывающей программы был
         сброшен и этот  листинг  показывает  как  устанавливать  контекст
         программы. Пример, приведенный в листинге 3-9, несколько неудачен
         тем, что не сохраняет контекст предыдущей программы, а просто пе-
         резаписывает его.
             При получении управления,  если  необходимо  сохранить целый
         набор регистров, наиболее легким способом выполнения этого явля-
         ется способ, заключающийся в том, чтобы сначала установить новый
         стек программы и затем  записать в него эти регистры.  Поскольку
         значения стекового сегмента и указателя стека не могут быть сох-
         ранены в стеке вызывающей программы  (в связи с отсутствием спо-
         соба получения их обратно) и поскольку  они не могут быть сохра-
         нены в новом стеке (который еще не установлен),  параметры стека
         должны сохраняться в памяти.  Если в виде исключения поместить в
         один и тот же сегмент программные коды и данные, то для сохране-
         ния старых  стекового  сегмента  и  указателя  стека и установке
         новых стекового сегмента и указателя стека может быть  использо-
         вана последовательность программных кодов,  показанная в листин-
         ге 3-11.



                                      - 3-58 -
             Листинг 3-11. Переключение стека для программы типа .EXE
         ----------------------------------------------------------------
         enter:   mov   cs:old_stk_seg,ss  ; Сохранение значений старого
                  mov   cs:old_stk_ptr,sp  ; стека
                  mov   ss,cs:new_stk_seg  ; загрузка значений нового
                  mov   sp,cs:new_stk_ptr  ; стека
                  push  ds        ; регистры стекового сегмента
                  push  es
                  push  ax        ; начало записи в стек общих регистров
                  ...
                  push  bp
                  push  si
                  push  di
                  ...
         body:<тело программы>    ; здесь начинается тело программы
                  ...
                  pop   di        ; начало восстановления общих регистров
                  pop   si
                  pop   bp
                  ...
                  pop   ax
                  pop   es        ; восстановление регистров сегмента
                  mov   ss,cs:old_stk_seg  ; восстановление старых зна-
                  mov   sp,cs:old_stk_ptr  ; чений стека
                  jmp   exit               ; обход памяти данных
         old_stk_seg    dw   ?    ; стековый сегмент вызывающей программы
         old_str_ptr    dw   ?    ; указатель стека вызывающей программы
         ; стековый сегмент подпрограммы
         new_stk_seg    dw   segment stack
         ; указатель стека подпрограммы
         new_stk_ptr    dw   top_of_stack
         exit:                             ; позиция выхода
                  ret             ; возврат в вызывающую программу
         ----------------------------------------------------------------
             Программные коды в листинге 3-11 зависят от имеющихся значе-
         ний для стекового сегмента и указателя стека,  уже размещенных в
         памяти. Для резидентных подпрограмм и подпрограмм RTL это должно
         быть выполнено с помощью процесса инициализации. Надлежащие зна-
         чения в память  для  программ  типа  .EXE  операционная  система
         MS-DOS помещает в процессе настройки.
             В связи с тем, что подпрограммы типа .COM не могут содержать
         значения сегмента,  эти подпрограммы требуют другого способа пе-
         реключения стеков.  Запоминание значений для вершины стека в па-
         мяти не вызывает проблем, за исключением адреса начала сегмента.
         Т.к. подпрограммы типа .COM для своих целей совместно используют
         один и тот же сегмент, то значение стекового сегмента может быть
         получено из регистра  программного сегмента. К несчастью, семей-
         ство микропроцессоров 8086 не поддерживает пересылку из регистра
         сегмента в  регистр сегмента,  поэтому значение может быть пере-
         дано косвенным путем. В связи с отсутствием регистра,  в котором
         можно было бы сохранить значение,  значение передается через па-
         мять,  используя  кодовый  сегмент. Для реализации этого способа
         начинайте подпрограмму со следующей инструкции:

          mov   cs:new_stk_seg,cs   ; получение нового стекового сегмента

             Если необходимо, то для переключения стеков в программе мож-

                                      - 3-59 -
         но разработать два макроса, содержащих требуемые программные ко-
         ды. Первый  макрос  включает  программный  код  из  входа в тело
         программы, а второй макрос программный код  из  выхода  из  тела
         программы. Оба  макроса должны соответствовать именам переменных
         стека в области данных, а второй макрос, кроме того, должен при-
         нимать метку вершины стека top_of_stack как параметр для включе-
         ния в предложение dw для указателя нового стека  new_stk_ptr.  В
         эти макросы  не должна входить инструкция RET.  Это позволит ис-
         пользовать эти макросы для выхода как с помощью инструкций JMP и
         IRET, так и с помощью инструкции RET.
             Для файлов типа .EXE второй макрос  должен  также  принимать
         как параметр имя стекового сегмента.  Пример описанных выше мак-
         росов для файлов типа .COM содержится в листинге  3-12 (INIT28),
         приводимом позднее в этой главе.

                 Дополнительные соображения по переключению стеков

             При переключении стеков, или, наоборот, при манипуляции сте-
         ковым сегментом программа уязвима для прерываний.  При изменении
         стекового сегмента,  но не указателя стека, или, когда возникает
         авария, должно бы произойти прерывание. В семействе микропроцес-
         соров  8086  это предотвращается путем изменения указателя стека
         сразу же после инструкции,  которая загружает стековый  сегмент.
         Когда один из процессоров семейства микропроцессоров 8086 загру-
         жает регистр сегмента (с помощью  инструкции MOV  или инструкции
         POP),  прерывание задерживается до тех пор, пока не будет выпол-
         нена следующая инструкция. Эта особенность позволяет благополуч-
         но обновлять регистр стекового сегмента и регистр указателя сте-
         ка. Это также объясняет то,  почему  отладчик  DEBUG  пропускает
         одну инструкцию  при  отслеживании  инструкции  MOV для регистра
         сегмента. Отладчик DEBUG выполняет программу в  пошаговом режиме
         благодаря установке флажка прерывания, который генерирует преры-
         вание #1,  следующее после большинства инструкций.  Т.к. при вы-
         полнении инструкции,  следующей за инструкцией переслать (MOV) в
         регистр сегмента,  прерывания запрещаются,  то отладчик DEBUG не
         получает управление до тех пор, пока не выполнит две инструкции,
         следующие за инструкцией MOV.
             В некоторых  случаях  не  всегда нужно обращаться к длинному
         переходу  при  переключении стеков,  демонстрируемому в листинге
         3-11. Некоторые  регистры  могут  помещаться  в  стек вызывающей
         программы много раз,  позволяя регистрам использоваться в  прог-
         рамме или,  по крайней мере, передавать новые значения в регистр
         стека. Конкретный программист должен сам решать  вопрос  о  том,
         как много текущего контекста сохранять в отдельной программе.
             Если переключение контекста используется с сопрограммами, то
         каждая подпрограмма  заканчивается  сохранением контекста другой
         подпрограммы. Хотя  это и излишне,  потому что только одной под-
         программе необходимо сохранять контекст другой  подпрограммы, но
         в  действительности  не  вредно.  Сопрограмма,  использующая эту
         структуру,  должна осуществлять выход только через функцию с ко-
         дом 4Ch "Завершить программу" так, чтобы MS-DOS смогла правильно
         завершить программу, независимо от состояния стека.
             Если параметры передаются из одной программы в другую и каж-
         дая программа поддерживает свой собственный стек, то для доступа
         к параметрам в стеке нельзя использовать регистр BP. Вместо это-
         го программисту  необходимо  извлечь значение стекового сегмента
         вызывающей программы и переслать его в любой из  регистров  сег-

                                      - 3-60 -
         мента DS  или  ES и выполнять доступ к памяти относительно этого
         регистра. Параметры затем можно прочитать  из  стека  вызывающей
         программы, даже  если вызываемая программа использует свой собс-
         твенный стек.

                 Введение в резидентную часть оперативной памяти

             В некоторых случаях MS-DOS сама реализуется  как резидентная
         программа. Взгляните снова на рис.3-15, и Вы увидите схему памя-
         ти для типичной MS-DOS версии 2.0 или выше.  (Заметим,  что  это
         исполнение необязательно  применять для версий MS-DOS,  выше чем
         версия 3.1). Все части MS-DOS, за исключением нерезидентной час-
         ти файла  COMMAND.COM,  располагаются  в  оперативной памяти все
         время. Программы пользователя осуществляют доступ к MS-DOS  пос-
         редством прерываний  или  переходов к прерываниям,  точно также,
         как выполнялись резидентные подпрограммы пользователя.
             Отдельные части  операционной  системы  являются  общими для
         всех систем MS-DOS и совместимы даже между  системами  различных
         номеров версий. Другие части систем являются уникальными для от-
         дельных номеров версий или отдельных аппаратных средств, работа-
         ющих под управлением MS-DOS. Различные компоненты. входящие в
         MS-DOS, и  атрибуты,  связанные с каждым компонентом,  показаны в
         таблице 3-6.  Названия компонентов могут изменяться от  версии  к
         версии,  но функции компонентов эквивалентны.  Файлы,  входящие в
         состав того или иного компонента, приведены в Руководстве пользо-
         вателя  для  той или иной версии MS-DOS.  Заметим,  что некоторые
         файлы могут быть "скрытыми" файлами,  которые не высвечиваются  в
         листинге каталога.  Однако, эти файлы еще располагаются и на дис-
         ке.
                                                         Таблица 3-6
                      Компоненты операционной системы MS-DOS
         ________________________________________________________________
                              |                 |
               Название       |     Атрибуты    |        Функция
         _____________________|_________________|________________________
         COMMAND.COM          |  совместимый    | Командный процессор
         _____________________|_________________|________________________
         IBMDOS.COM или другой|  совместимый    | Обслуживание системы
         _____________________|_________________|________________________
         IBMBIO.COM или другой|  независимый от | Интерфейс ROM-BIOS или
                              |  системы        | BIOS
         _____________________|_________________|________________________
         ROM-BIOS             |  независимый от | BIOS, базируемая на
                              |  системы        | ROM (несколько)
         _____________________|_________________|________________________


                     ROM-BIOS в сравнении с загружаемой BIOS

             Имеются две основные области различий,  которые могут возни-
         кать внутри операционных систем MS-DOS различных исполнений. Эти
         различия очень существенно влияют на то,  что можно выполнять, и
         что нельзя  выполнять  для компонентов,  функционирующих в рези-
         дентной части оперативной памяти.  Одна из таких областей разли-
         чий формируется в зависимости от того,  где первоначально распо-
         лагается BIOS (Basic Input/Output System - базовая система ввода
         /вывода) для  аппаратных средств системы в ROM (read-only memory

                                      - 3-61 -
         - постоянное запоминающее устройство - ПЗУ) или в файле, который
         может быть загружен с диска. Воздействие этих альтернативных ва-
         риантов исполнения состоит в том, что BIOS, расположенная в ПЗУ,
         обеспечивает заданную среду для этой отдельной машины, в то вре-
         мя как загружаемая BIOS часто недоступна  для  программиста.  (В
         отличие от  систем CP/M,  поставщики операционной системы MS-DOS
         не обеспечивают пользователей исходными  листингами  загружаемой
         BIOS).
             Важность этой опции заключается в том, что MS-DOS не является
         реентерабельной (повторно-входимой)!  Т.е.,  если написана  рези-
         дентная подпрограмма, которая либо управляется прерываниями, либо
         помещает "заплаты" в векторы прерываний MS-DOS,  то  подпрограмма
         не  может  вызывать MS-DOS!  Аппаратно MS-DOS поддерживает только
         один набор буферов внутренних данных,  и любая попытка повторного
         входа приводит к полному отказу системы.  Т.к. MS-DOS не является
         реентерабельной,  она не может быть использована  для  выполнения
         ввода/вывода или поддержки функций для резидентных программ,  уп-
         равляемых прерываниями.  Это ограничение может быть снято  тогда,
         когда  фирма "Майкрософт" выпустит конкурентную версию MS-DOS,  в
         которой,  надеемся,  будет обеспечен способ обработки таких собы-
         тий.  До этих пор  программисты, желающие писать резидентные под-
         программы, должны,  вероятно, рассчитывать на ROM-BIOS или писать
         свои собственные подпрограммы драйверов. Все эти опции приводят в
         результате к непереносимому программному коду,  но иногда  это  и
         есть цена платы за предоставляемые возможности.
             Если BIOS,  на самом деле,  загружается с диска во время  на-
         чальной загрузки системы,  то почти наверняка для обеспечения ин-
         терфейса с аппаратными средствами пользователи будут  писать свои
         собственные подпрограммы. В отличие от связи между обычными прог-
         раммами и MS-DOS,  которые используют векторы прерываний,  MS-DOS
         общается с BIOS посредством инструкций CALL и JMP. MS-DOS не име-
         ет стандартной таблицы переходов для BIOS  (типа  системы  CP/M),
         которая могла бы использоваться прикладным программистом, так как
         можно легко заметить,  что иметь BIOS, базируемую на ROM, гораздо
         ценнее,  чем  писать резидентные подпрограммы для доступа к аппа-
         ратным средствам.

                   Прерывания в сравнении с системами с опросом

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

                      Внесение "заплат" в векторы прерываний

             Резидентные подпрограммы активизируются в одном из двух слу-
         чаев:  они инициирутся  с помощью прерываний аппаратных средств
         (управление событиями), или они должна поместить "заплату" в су-
         ществующую систему (управление перехватом).  Возможна также ком-
         бинация этих способов,  где точка "заплаты"  является  одним  из
         прерываний аппаратных средств. Если используемая система не под-
         держивает прерывания аппаратных средств, то следует использовать
         способ "заплат".
             Для доступа (с некоторыми сложностями)  к  резидентным  под-
         программам могут   быть   использованы   прерывания   аппаратных
         средств,  не  используемые операционной системой MS-DOS.  До тех
         пор,  пока программа не осуществляет вызов MS-DOS,  никаких сис-
         темных конфликтов не произойдет. Если аппаратные средства систе-
         мы доступны с помощью резидентной подпрограммы,  то  она  должна
         проверить,  что  в  это  время  нет  больше доступных аппаратных
         средств, и быть осторожной при восстановлении аппаратного средс-
         тва  в  его  исходное  состояние.  Примером минимального влияния
         подпрограммы управления прерываниями является программа сохране-
         ния  всех регистров текущего выполнения программы в зарезервиро-
         ванном разделе памяти,  когда происходит внешнее прерывание. Та-
         кая подпрограмма полезна при отладке программ реального времени.
         Однако,  если используемое прерывание также используется  систе-
         мой, то подпрограмма должна принимать во внимание управление пе-
         рехватом,  потому что  резидентная  подпрограмма  установлена  с
         "заплатой".
             Способ "заплат" является способом вставки резидентных  подп-
         рограмм в  обычное исполнение системы в данной точке так,  чтобы
         доступ в эту точку осуществлялся через резидентную подпрограмму.
         Примером вставки "заплат", вызывающей аппаратное прерывание, яв-
         ляется установка резидентной подпрограммы  управления  клавиату-
         рой. Для  этого вектор прерывания клавиатуры изменяется на точку
         для резидентной подпрограммы.  Значение предыдущего вектора кла-
         виатуры запоминается в адресе назначения инструкции перехода far
         (далекий), которая используется при выходе из подпрограммы рези-
         дентной памяти.  Когда происходит прерывание клавиатуры, начина-
         ется выполнение резидентной подпрограммы. После завершения обра-
         ботки прерывания,  резидентная  подпрограмма передает управление
         драйверу клавиатуры. Если резидентная подпрограмма действительно
         использует ввод клавиатуры в некоторых случаях, которые не может
         продолжить драйвер клавиатуры, то резидентная подпрограмма долж-
         на сама обслужить и очистить прерывание, и затем вернуться в вы-
         зывающую программу по инструкции IRET. Во всех случаях резидент-
         ная подпрограмма должна сохранять контекст прерванной программы.
             Другими возможными точками вставки "заплат",  которые не вы-
         полняют  использование  прерываний аппаратных средств,  является
         вставка "заплат" в  один  из  векторов  прерывания  программного
         обеспечения или в адрес перехода.  Вставка "заплат" в MS-DOS че-
         рез векторы прерываний программного обеспечения обычно не  дела-
         ется потому,  что в операционной системе MS-DOS отсутствует воз-

                                      - 3-63 -
         можность распознавания таблицы переходов.  Кроме того, в связи с
         тем, что  не существует стандартный интерфейс между MS-DOS и ин-
         терфейсом ее BIOS,  вставка "заплат" между MS-DOS и BIOS  обычно
         затруднительна. Использование  прерываний программного обеспече-
         ния остается проблематичным.
             Одним  из  общих  мест вставки "заплат" в векторы прерываний
         MS-DOS является прерывание  int 28h. Это, по-видимому, вспомога-
         тельное прерывание, используемое внутри  MS-DOS.  Это также одна
         из точек вставки  "заплат",  в которую гарантируется частый дос-
         туп. Резидентная подпрограмма, вставленная в качестве  "заплаты"
         в этой точке,  не  может  вызывать драйверы функций MS-DOS, т.к.
         это приведет к сбою системы. Резидентная подпрограмма также дол-
         жна  использовать  свой  собственный контекст для предотвращения
         изменения существующего стека и регистров.  Листинг 3-12 показы-
         вает  программные  коды,  необходимые для установки  резидентной
         подпрограммы в прерывании  int 28h и поддержки  этой резидентной
         подпрограммы.

           Листинг 3-12. Программа INIT28 - Вставка "заплат" в векторы
                                прерываний системы
         ----------------------------------------------------------------

         ; ==== INIT28 - Этот файл вырабатывает программу формата .COM ==
         ; ==== Установка резидентной подпрограммы путем вставки ========
         ; ==== "заплаты" в прерывание int 28h ==========================
         PAGE    60,132
         ; ==== Соответствует установке прерывания ======================
         VECT_NUM EQU    28h          ; номер вектора для установки
         OFF     EQU     0h           ; подпрограмма неактивна
         ON      EQU     0FFFFh       ; подпрограмма активна
         ;
         INCLUDE STDMAC.INC          ; включение описания макробиблиотеки
         ; ==== НАЧАЛО ПРОГРАММНОЙ СЕКЦИИ ===============================
         init28  SEGMENT
                 ASSUME  cs:init28
                 ASSUME  ds:init28
                 ORG     0
         SEG_ORG EQU     $
                 ORG     0100h
         main    PROC    FAR
         start:  jmp     init         ; пропуск памяти "старого вектора"
         old_v   dd      ? ; пространство для запоминания старого вектора
         entry:  jmp     first        ; пропуск "идентификации"
                 db      'TEST ROUTINE'
         first:  @SwapNewStack     ; макрос для переключения в новый стек
                 cmp     go_switch,ON ; проверка если я активна
                 jne     bypass       ; да - продолжить для выхода
                 mov     go_switch,OFF ; нет - установка переключателя
                                       ; активна
         ;
         ;  < ЗДЕСЬ ИДЕТ ВАША РЕЗИДЕНТНАЯ ПОДПРОГРАММА >
         ;
                 mov     go_switch,ON ; установка состояния неактивна
         bypass: @SwapOLDStack    ; установка стека (и включение данных)
                 jmp     cs:exit  ; переход к подпр-ме обслуж-я прерыв-я
         exit            dd     ?
         go_switch       dw     ?

                                      - 3-64 -
                         db     32 dup ('stack   ')
         TOS             EQU    $
         LAST_BYTE       EQU    $
         ;
         ; ===== СЕКЦИЯ ИНИЦИАЛИЗАЦИИ - ПОСЛЕ ЗАГРУЗКИ НЕ НУЖНА =========
         ;
         init:   mov     go_switch,OFF ; предупреждение активизации
                 mov     ah,35h        ; получение адреса вектора
                 mov     al,VECT_NUM
                 @DosCall
                 mov     word ptr exit,bx ; сохранение указателя IP для
                                          ; выхода
                 mov     word ptr exit+2,es ; сохранение указателя CS для
                                            ; выхода
                 mov     word ptr old_v,bx  ; сохранение указателя IP для
                                            ; удаления
                 mov     word ptr old_v+2,es ; сохранение указателя CS
                                             ; для удаления
                 mov     ah,25h         ; установка нового указателя
                 mov     al,VECT_NUM
                 mov     dx,offset entry ; установка указателя IP
                                         ; ... (CS и DS аналогично)
                 @DosCall
                 mov     go_switch,ON
                 mov     dx,9offset LAST_BYTE - SEG_ORG + 15) shr 4
                 mov     ah,31h   ; завершить и оставить резидентной
                 @DosCall
         ;
         main    ENDP
         init28  ENDS
                 END     start
         ----------------------------------------------------------------

             Другие возможные  точки вставки "заплат" зависят от типа ре-
         зидентной подпрограммы и частоты, с которой она должна вызывать-
         ся.  Например,  подпрограмма  буферизации печати - print spooler
         routine (которая печатает файлы во время выполнения других прог-
         рамм)  не только должна прерывать прерывание для активации пере-
         дачи символов в принтер, но также должна прерывать любое обраще-
         ние   к  MS-DOS,  которое  использует  принтер,  так,  чтобы  не
         возникали конфликты.  Рис.3-16 показывает прерывание буферизации
         печати int 28h для активации самой себя и прерывание int 21h для
         охраны самой себя от конфликтов при доступе к принтеру.


                                      - 3-65 -
                                  ---------------
                прерывание       |     ...       |  Таблица векторов се-
            ---------------------| Int 21 IP/CS  |<---------------------
           |    прерывание       |     ...       |  мейства микропроцес-|
           |  -------------------| Int 28 IP/CS  |<-----    соров 8086  |
           | |                   |     ...       |      |               |
           | |                    ---------------       |Внешний вызов  |
           | |                    ---------------       |   Int 28h     |
           | |            ------>|    MS-DOS     |------     MS-DOS     |
           | |           |       |     ...       |                      |
           | |   --------|-------|  Коды Int 21  |<-----------------    |
           | |  |        |       |     ...       |                  |   |
           | |  |         -------|  Коды Int 28  |<------- Возврат  |   |
           | |  |                 ---------------        |    в     |   |
           | |  |                 ---------------        | MS-DOS   |   |
           |  --|--------------->| Подпрограмма  |       | из пре-  |   |
           |    |                |  буферизации  |       | рывания  |   |
           |    |                |     ...       |       | Int 21h  |   |
           |    |                |  Коды печати  |-------           |   |
           |    |                |     ...       |                  |   |
           |    |                |---------------|Передача Int 21h  |   |
            ----|--------------->|Проверка Int 21|------------------    |
                |                 ---------------                       |
                |                 ---------------                       |
                |                |   Программа   |                      |
                |                |  пользователя |                      |
                |                |     ...       |                      |
                 --------------->| Int 21h |----------------------------
            Возврат Int 21 в     |     ...       |
            программе пользова-   ---------------
                   теля

            Рис.3-16. Использование векторов прерываний подпрограммой
                                буферизации печати

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

                    REMOVE - пример интегрированной программы

             Программа удаления REMOVE (смотри листинг 3-13) предназначе-
         на для удаления установленной резидентной программы и базируется
         на  примере,  данном  в INIT28 (смотри листинг 3-12).  Программа
         REMOVE пытается  идентифицировать  резидентную  программу  путем

                                      - 3-66 -
         дампирования байтов,  следующих за точкой входа,  и отображает 4
         байта,  предшествующих точке входа, как адрес предыдущего векто-
         ра.  Кроме того,  программа REMOVE предполагает, что резидентная
         программа представлена в формате .COM, и пытается разместить ад-
         реса PSP и блока среды. Всю эту информацию программа REMOVE пре-
         доставляет пользователю для принятия решения о том,  удалять ре-
         зидентную программу, вставленную в вектор прерывания, или нет.

            Листинг 3-13. REMOVE - удаление резидентной подпрограммы,
                    вставленной в качестве "заплаты" в вектор
                                    прерывания
         ----------------------------------------------------------------
         PAGE    60,132
         ;===== REMOVE - Этот файл генерирует программу типа .COM =======
         ;===== Удаление резидентной подпрограммы, вставленной в ка- ====
         ;============ честве "заплаты" в вектор прерывания =============
         ;(Interrupt Service Routine (ISR) - это подпрограмма обслужива-
         ; ния прерывания))
         OLD_IP  EQU    -4         ; Возможное положение IP в ISR
         OLD_CS  EQU    -2         ; Возможное положение CS в ISR
         ID      EQU    0          ; Положение 1-го байта в ISR
         IRETOP  EQU    0CFh       ; Код операции IRET
         ;
         ;===== МАКРООПРЕДЕЛЕНИЯ ДЛЯ УТИЛИТ =============================
         ;
         INCLUDE STDMAC.INC        ; Включение макроопределений
         ;
         remove  SEGMENT
                 ASSUME cs:remove
                 ASSUME ds:remove
         ; Определение необходимых адресов внутри сегмента программого
         ; префикса (PSP)
                 ORG    2Ch
         env_adr LABEL  WORD       ; Адрес указателя среды
                 ORG    80h
         cmd_len db     ?          ; Длина командной строки
         new_len db     ?          ; Длина строки буферизованного чтения
         cmd_buf db     ?          ; Строка командной строки
         ;===== НАЧАЛО ПРОГРАММНОГО КОДА ================================
                 ORG    0100h
         main    PROC   FAR
         start:
                 mov    ch,byte ptr [cmd_len]
                 cmp    ch,0       ; аргумент обеспечен ?
                 jnz    have_cmd
         ; Аргумент не обеспечен - приглашение пользователя для указания
         get_cmd:
                 @DisStr request   ; приглашение для номера вектора
                 mov    byte ptr [cmd_len],80
                 mov    dx,offset cmd_len
                 mov    ah,0Ah     ; выполнение буферизованного чтения в
                 @DosCall          ; буфер командной строки
                 @DisChr LF        ; новая строка
                 mov    ch,new_len ; получение размера введенного текста
                 cmp    ch,0       ; просмотр, ответил ли пользователь?
                 jz     abort      ; если нет, то предполагается выход
                 inc    ch         ; установка ответа для приведения в

                                      - 3-67 -
                                   ; соответствие
         have_cmd:
                 cmp    ch,3       ; проверка для правильного # символов
                 je     ok_cmd
                 @DisStr bad_cmd   ; если ошибка, то некорректный флажок
         abort:  jmp    finis
         ok_cmd: mov    bx,offset cmd_buf
                 mov    ch,2       ; грамматический разбор 2 символов
                 call   get_hex    ; преобразование # в буфер в двоичн.
                 jc     abort      ; выход, если ошибка преобразования
                 mov    vec_num,al ; сохранение адреса вектора
                 mov    ah,35h    ; получение указателя вектора из MS-DOS
                 @DosCall
                 mov    vec_ip,bx  ; сохранение IP вектора
                 mov    al,vec_num ; восстановление номера вектора
                 call   show_vector ; отображение содержимого вектора
                 @DisStr askresv
                 call   yesno
                 jc     no_restore ; нельзя желать восстановить вектор
         ;
         ; ВОССТАНОВЛЕНИЕ ВЕКТОРА ПО АДРЕСУ В ПОДПРОГРАММЕ
                 mov    bx,vec_ip  ; получение адреса подпрограммы
                 mov    dx,es:OLD_IP[bx] ; получение IP старого вектора
                 mov    cx,es:OLD_CS[bx] ; получение CS старого вектора
                 mov    al,vec_num ; получение номера вектора
                 push   ds         ; сохранение текущего DS
                 mov    ds,cx      ; установка назначения вектора
                 mov    ah,25h     ; установка адреса вектора
                 @DosCall
                 pop    ds         ; восстановление сегмента данных
         ;
         ; Отображение адреса среды и выдача приглашения для удаления.
         ; Адрес среды будет действительным, если это программа типа .COM
         no_restore:
                 @DisStr askremb   ; отображение адреса среды
                 mov    ax,es:env_adr ; получение адреса среды
                 mov    ch,4
                 call   bin2hex   ; отображение возможного сегмента среды
                 @DisStr ip0
                 call   yesno
                 jc     no_env     ; обход удаления среды
         ;
         ; УДАЛЕНИЕ БЛОКА СРЕДЫ
                 push   es    ; сохранение сегмента главной подпрограммы
                 mov    cx,es:env_adr ; получение адреса среды
                 mov    es,cx      ; и подготовка к удалению
                 call   rem_mem    ; попытка удалить блок
                 pop    es  ; восстановление адреса главной подпрограммы
         ;
         ; Отображение адреса сегмента главной подпрограммы и выдача
         ; приглашения для его удаления
         no_env:
                 @DisStr askremm   ; отображение адреса главного блока
                 mov    ax,es      ; адрес главного блока
                 mov    ch,4
                 call   bin2hex
                 @DisStr ip0

                                      - 3-68 -
                 call   yesno
                 jc     finis     ; нельзя желать удаления главного блока
         ;
         ; УДАЛЕНИЕ ГЛАВНОГО БЛОКА ПАМЯТИ РЕЗИДЕНТНОЙ ПОДПРОГРАММЫ
                 call   rem_mem    ; попытка удаления блока
         ;
         finis:  mov    ax,4C00h   ; завершение программы
                 @DosCall
         ;
         vec_num db     ?         ; память для запоминания номера вектора
         vec_ip  dw     ?          ; память для запоминания IP вектора
         ; Номер удаляемого вектора
         request db     'Vector number to remove: $'
         ; Аварийное завершение из-за ошибки в командной строке
         bad_cmd db     'Command Line format error - aborting',CR,LF,'$'
         ; Восстановить вектор из старого ?
         askresv db     'Restore Vector from Old? $'
         ; Удаление блока среды
         askremb db     'Remove Environment Block: $'
         ; Удаление блока главной программы
         askremm db     'Remove Main Program Block: $'
         ip0     db     ':0000 $'
         ;
         main    ENDP
         ;
         ; == REM_MEM использует функцию 49 (шестн.) MS-DOS для попытки =
         ; ======= перераспределения блока памяти, адресуемого ES =======
         ;
         rem_mem PROC   NEAR
                 push   ax         ; сохранение регистров
                 push   cx         ;    используемых
                 push   dx         ; @DisStr и  @Dischr
                 mov    ah,49h     ; освобождение распределенной памяти
                 @DosCall
                 jnc    free_ok ; нет ошибок - выдача сообщения об успехе
                 push   ax         ; сохранение кода ошибки
                 @DisStr fail      ; информирование о сбое
                 pop    ax         ; и выдача кода ошибки
                 mov    ch,4       ; (все 4 цифры)
                 call   bin2hex
                 @DisChr CR
                 @DisChr LF
                 jmp    rem_exit
         free_ok:
                 @DisStr pass
         rem_exit:
                 pop    dx         ; восстановление регистров
                 pop    cx
                 pop    ax
                 ret
         ; Успешное освобождение распределенной памяти
         pass    db     'Successful Free Allocated Memory',CR,LF,'$'
         ; Сбой при освобождении распределенной памяти - код ошибки
         fail    db     'Failed to Free Allocated Memory - Error Code: $'
         rem_mem ENDP
         ;
         ; ===== YESNO приглашает пользователя ответить либо Y, либо N. =

                                      - 3-69 -
         ; ===== Если введено Y (да), то YESNO возвращает без переноса ==
         ; ===== (NC). Если введено N (нет) или <RET>, то YESNO возвра- =
         ; ===== щает с переносом (CY). =================================
         yesno   PROC   NEAR
                 push   ax
                 push   dx
                 @DisStr prompt    ; приглашение пользователя для ввода
         retry:  mov    ah,08h     ; получение ответа (no echo - нет эха)
                 @DosCall
                 @Case  al,<'y','Y','n','N',CR>,<yes,yes,no,no,no>
                 @DisChr 07h       ; неправильный ответ - гудок
                 jmp    retry      ; и ожидание нового ответа
         no:     @DisStr 'N'
                 stc
                 jmp    yn_exit
         yes:    @DisChr 'Y'
                 clc               ; очистка переноса
         yn_exit:
                 @DisChr CR
                 @DisChr LF
                 pop    dx
                 pop    ax
                 ret
         prompt  db     ' (Y/N): $',
         yesno   ENDP
         ;
         ; ===== SHOW_VECTOR отображает содержимое отмеченных ячеек =====
         ; ===== в ES:BX в шестнадцатиричном формате и в формате ASCII. =
         ; ===== Так как это используется внутри отображения вектора, ===
         ; ===== то она также показывает AL в шестнадцатиричном формате =
         ; ===== как номер вектора,  и информирует пользователя, если ===
         ; ===== в инструкции IRET отмечен первый байт. =================
         ; ===== SHOW_VECTOR также отображает два слова, размещенные ====
         ; ===== перед адресом вектора как CS:IP, в случае если =========
         ; ===== пользователь запомнил там адрес старого вектора при ====
         ; ===== установке. =============================================
         ;
         show_vector    PROC NEAR
                 push   cx         ; сохранение регистров,
                 push   dx         ;     используемых
                 push   ax         ; @DisChr и @DisStr
                 @DisStr vmsg1     ; начало отображения сообщений
                 pop    ax         ; восстановление значения AL
                 push   ax
                 mov    ah,al
                 mov    ch,2       ; отображение двух шестнадц. цифр
                 call   bin2hex
                 @DisStr vmsg2     ; показ потенциального адреса воосст-я
                 mov    ax,es:OLD_CS[bx] ; получение возможн. значения CS
                 mov    ch,4
                 call   bin2hex    ; отображение возможного старого CS
                 @DisChr ':'
                 mov    ax,es:OLD_IP[bx] ; получение возможн. значения CS
                 call   bin2hex    ; отображение возможного старого CS
                 cmp    byte ptr es:ID[bx],IRETOP
                 jne    noiret     ; это инструкция IRET?
                 @DisStr vmsg3

                                      - 3-70 -
         noiret: @DisChr CR
                 @DisChr LF
                 mov    cl,16      ; дамп 16 байтов
                 call   dump       ; показ шестнадц. и ASCII значений
                 pop    ax
                 pop    dx
                 pop    cx
                 ret
         vmsg1   db     'Vector # $'
         vmsg2   db     '  Old Vector: $'
         vmsg3   db     'IRET$'
         show_vector    ENDP
         ;
         ; ===== DUMP отображает содержимое ячеек, отмеченных с помощью =
         ; ===== ES:BX, в шестнадцатиричном формате и в формате ASCII. ==
         ; ===== Содержимое CL # отображаемых байтов. ===================
         dump    PROC   NEAR
                 push   ax         ; сохранение регистров,
                 push   dx         ;     используемых
                 push   bx         ; @DisChr и @DisStr
                 push   cx
                 @DisStr dmsg1     ; начало отображения сообщений
                 mov    ch,2       ; 2 шестнадцатиричные цифры на байт
         h_dump: mov    ah,es:[bx] ; получение байта
                 jnc    bx         ; следующий байт
                 call   bin2hex
                 @DisChr ' '
                 dec    cl         ; счетчик цикла - 1
                 jnz    h_dump     ; повторение, пока счетчик не станет 0
                 @DisStr dmsg2     ; следующий раздел
                 pop    cx         ; восстановление значений
                 pop    bx         ;     BX (индекс)
                 push   bx         ;        и
                 push   cx         ;     CX (счетчик)
         t_dump: mov    al,es:[bx] ; получение байта
                 jnc    bx         ; следующий байт
                 cmp    al,' '     ; проверка возможного диапазона печати
                 jb     no_prnt    ; ? < памяти
                 cmp    al,7Eh     ; DEL невозможно напечатать, либо
                 ja     no_print
                 @DisChr al        ; возможно - выполнить так ...
                 jmp    nxt_txt
         no_prnt:
                 @DisChr '.'       ; используйте "." для невозможн.печати
         nxt_txt:
                 dec    cl         ; счетчик цикла - 1
                 jnz    t_dump     ; повторение, пока счетчик не станет 0
         ; Выполнение всех восстановлений и выход
                 @DisChr CR
                 @DisChr LF
                 pop    cx         ; восстановление регистров
                 pop    bx
                 pop    dx
                 pop    ax
                 ret
         dmsg1   db     'HEX: $'
         dmsg2   db     ' ASCII: $'

                                      - 3-71 -
         dump    ENDP
         ;
         ; ===== GET_HEX осуществляет грамматический разбор буфера, =====
         ; ===== указанного в BX, возвращаемое количество в AX. =========
         ; ===== # цифр для грамматического разбора содержится в CH, ====
         ; ===== и BX увеличивается на # обработанных цифр. =============
         ;
         get_hex PROC   NEAR
                 push   dx         ; сохранение регистра DX
                 push   cx         ; сохранение регистра CX
                 mov    ax,0       ; очистка аккумулируемого #
                 mov    dh,0       ; очистка верхней рабочей памяти
                 mov    cl,4       ; установка сдвига счетчика
         nxt_digit:
                 mov    dl,[bx]    ; получение символа
                 sub    dl,'0'
                 jb     bad_digit  ; ? < '0' - неправильно
                 cmp    dl,0Ah
                 jb     ok_digit   ; от '0' до '9' - хорошо
                 sub    dl,'A'-'0'
                 jb     bad_digit  ; '9' < ? < 'A' - неправильно
                 add    dl,0Ah
                 cmp    dl,10h
                 jb     ok_digit   ; от 'A' до 'F' - хорошо
                 sub    dl,'a'-'A'-0Ah
                 jb     bad_digit  ; 'F' < ? < 'a' - неправильно
                 add    dl,0Ah
                 cmp    al,10h
                 jae    bad_digit  ; 'f' < ? - неправильно
         ok_digit:
                 add    ax,dx      ; аккумулирование цифр в AX
                 inc    bx         ; следующая цифра
                 dec    ch
                 jnz    more_digit ; еще цифры для аккумулирования?
                 clc               ; ошибки нет - очистка CY
                 pop    cx
                 pop    dx
                 ret
         more_digit:
                 shl    ax,cl      ; открытие места для следующей цифры
                 jmp    nxt_digit  ; цикл для следующей цифры
         bad_digit:
                 @DisStr digit_error ; информирование об ошибке элемента
                 stc               ; ошибка - установка переноса
                 pop    cx
                 pop    dx
                 ret
         ; ожидался двухцифровой шестнадцатиричный номер
         digit_error db 'A two-digit hex number was expected',CR,LF,'$'
         get_hex ENDP
         ;
         ; ===== BIN2HEX отображает значение, содержащееся в AX как =====
         ; ===== шестнадцатиричный #. Регистры не портятся. CH содержит =
         ; ===== # цифры для отображения, выбираемой слева направо в AX.=
         ; ===== (AH отображается, если CH равен 2). ====================
         ;
         bin2hex PROC   NEAR

                                      - 3-72 -
                 push   ax         ; сохранение всех регистров
                 push   bx
                 push   cx
                 push   dx
                 mov    cl,4       ; установка счетчика чередования
                 mov    bx,ax      ; копирование AX для работы
         ; Начало цикла DIGIT (цифра) для обработки   цифр
         moredig:
                 rol    bx,cl      ; преобразование двоичн.-шестнадцат.
                 mov    al,bl
                 and    al,0Fh
                 add    al,90h
                 daa
                 adc    al,40h
                 daa
         ; Отображение цифры и проверка на последующие цифры - восста-
         ; новление, если нет
                 @DisChr al
                 dec    ch
                 jnz    moredig
                 pop    dx
                 pop    cx
                 pop    bx
                 pop    ax
                 ret
         bin2hex ENDP
         ;
         remove  ENDS
                 END    start
         ----------------------------------------------------------------


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

                                    Заключение

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

             Глава 4. ПРОГРАММЫ TSR (ЗАВЕРШИТЬ И ОСТАВИТЬ РЕЗИДЕНТНОЙ)

             Обзор
             Работа с аппаратурой PC
             Работа в среде DOS
             Загрузка и инициализация TSR
             Реактивация, архитектура DOS и сервис
             Фоновая обработка с использованием Int 28h
             Удаление из памяти программ TSR
             Заключение

              Создание и установка резидентных программ (TSR) является ши-
         роко  используемым  средством,  но  его функционирование остается
         скрытым от большинства пользователей.  Архитектура MS-DOS и аппа-
         ратные  средства PC налагают ограничения на возможности TSR и ус-
         ловия их осуществления. Некоторые из этих ограничений проявляются
         только тогда,  когда TSR выводятся на экран или запрашивают базо-
         вую систему ввода-вывода (BIOS) с прерыванием программ обслужива-
         ния; другие требуют внимания при установке TSR.
              Эта глава описывает работу TSR. Вы будете ознакомлены с  об-
         служиванием (документированным и нет),  которое обеспечивает DOS,
         и со взаимодействием TSR с DOS.  Вы будете  также  ознакомлены  с
         несколькими  техническими  решениями автора относительно TSR.  Но
         вначале несколько слов предупреждения.
              Многие материалы, использованные в этом описании не докумен-
         тированы,  а получены реассемблированием PC-DOS версии 3.10. Мно-
         гие обсуждаемые здесь программы не доступны в версиях PC-DOS ниже
         3.00, и нет гарантий, что они будут представлены в следующих вер-
         сиях DOS.  Многие особенности характерны для программного обеспе-
         чения PC-DOS 3.10, которое их использует, и не могут быть перене-
         сены в другие выпуски DOS,  но могут быть использованы в будущем.
              Это делает возможным конфликты между различными существующи-
         ми TSR (включая представленные здесь примеры). Степень этих конф-
         ликтов может находиться в диапазоне от досадных до катастрофичес-
         ких. Наиболее серьезные из них могут привести к потере данных или
         порче диска.
              Дополнительно в   этой  главе  описывается  программирование
         контроллера CRT 6845,  который поддеpживает монохромный и цветной
         дисплейные  адаптеры (MDA и CGA). Ошибки в программировании этого
         устройства могут привести к серьезному повреждению Вашей системы.

                                       Обзор

              Резидентные программы становятся повсеместными. Они доступны
         как коммерческие программы, совместно используемые средства и да-
         же как часть MS-DOS.  Sidekick (боковой удар) Борланда  является,
         вероятно,  наиболее известным коммерческим предложением.  Команды
         PRINT и ASSIGN и несколько других утилит DOS также являются рези-
         дентными.
              Все резидентные программы начинают жизнь,  как обычные прог-
         раммы.  После выполнения такой программы часть ее кода остается в
         памяти. Код, который выполняется при первом обращении, называется
         кодом инициализации,  а тот, который остается после, известен как
         резидентный код.  Основной задачей  кода  инициализации  является
         подготовка резидентного кода для дальнейшего использования.  Воз-
         можности кода инициализации не  ограничены,  но  программирование

                                      - 4-2 -
         резидентного кода может быть сложным.
              Резидентные программы могут быть сгруппированы в три катего-
         рии на основе того,  что их резидентный код делает.  Члены первой
         группы не обеспечивают взаимодействия пользователя с их резидент-
         ной частью.  Однажды загруженные, они остаются фоновыми, выполняя
         свои  задачи  без  обращения  к  базовой   системе   ввода-вывода
         (BIOS).Команда  DOS ASSIGN является одним из таких резидентов; ее
         резидентная часть обеспечивает доступ и переназначает запрошенный
         диск  с  одного драйвера на другой.  Подробное документирование и
         многочисленные примеры делают написание  этого  типа  резидентных
         программ несложной задачей.
              Члены второй группы резидентных программ  остаются  приоста-
         новленными  до  выдачи специального запроса пользователя.  Обычно
         этот запрос производиться  нажатием  функциональной  клавиши  или
         комбинацией   функциональной   клавиши   с   другими   (например,
         Alt-Shift), причем функциональная клавиша нажимается последней. С
         другой  стороны,  их резидентный код не делает запросов к базовой
         системе ввода-вывода; они должны обслуживаться командами DOS, та-
         кими, как чтение и запись, только во время инициализации.
              Небольшая резидентная телефонная база данных попадает  в эту
         группу.  Код  инициализации  считывает полное ее оглавление в па-
         мять.  В ответ на нажатие функциональной клавиши резидентный  код
         должен  сохранить  текущий экран,  получить одно или больше имен,
         найти ассоциированные с ними номера телефонов  и  отобразить  ре-
         зультаты поиска. Когда имен больше нет, резидент должен восстано-
         вить начальный экран и выключиться.
              Обслуживание, необходимое  для таких TSR,  достаточно хорошо
         документировано,  но есть несколько технических  решений  относи-
         тельно  распределения  используемых  клавиш и связи с аппаратными
         средствами отображения.
              Последняя группа резидентных программ осуществляет асинхрон-
         ные запросы к базовой системе ввода-вывода.  Эти программы запус-
         каются  нажатием  функциональной  клавиши  или  каким-либо другим
         программным прерыванием от аппаратных средств, (например,  тайме-
         ром). Этот резидентный код не обязательно имеет связь с пользова-
         телем.  К этой категории относится  утилита DOS PRINT.  Эти рези-
         дентные программы трудны для написания, потому что DOS в основном
         является однопользовательской/однопрограммной  системой.  Майкро-
         софт  имеет средство отладки для таких программ,  но оно не доку-
         ментировано и для правильного использования требует нестандартных
         соглашений DOS .
              Перед тем,  как начать писать резидентную программу, Вам бу-
         дет нужна некоторая дополнительная информация. Например, для под-
         держки "горячих" функциональных клавиш, Вы должны знать,как рабо-
         тает клавиатура и дисплей. Или,например, архитектура программного
         обеспечения DOS налагает некоторые реальные  ограничения  на  то,
         что могут делать резидентные программы; Вы должны знать о работа-
         ющих в версии DOS модулях,  которые воздействуют на TSR.  Раз  Вы
         понимаете  работу аппаратуры и механизм операционной системы,  Вы
         должны быть готовы изучить, что TSR требует при своей инициализа-
         ции и реактивации. И, наконец, Вы узнаете,как писать TSR, которая
         выполняется в фоновом разделе.

                              Работа с аппаратурой PC

              Клавиатура, таймер и некоторые другие устройства при обраще-
         нии к процессору генерируют прерывания. Системы PC/XT поддержива-

                                      - 4-3 -
         ют восемь различных программных прерываний,а системы AT - больше.
         Многие из этих прерываний относятся к драйверам устройств, и TSR
         не должны взаимодействовать с ними.Из всех программных прерываний
         TSR  взаимодействует только с прерываниями от таймера и клавиату-
         ры.
              Часть обращений   к  резидентным  программам  осуществляется
         пользователем при работе с  "горячими"  ключами.  Одним  нажатием
         клавиши  можно  вызвать  TSR и запросить выполнение какой-либо ее
         функции.  Если программа написана грамотно, она сразу включается,
         выполняет  свою  работу и уходит в фоновый раздел без повреждения
         или разрушения других программ.  Осуществление запросов через го-
         рячие  ключи  требует небольшой работы и хорошего понимания,  как
         функционируют дисплей и клавиатура.
              Некоторые резидентные программы должны выполнять свои задачи
         через точно заданные периоды. Каждый персональный компьютер имеет
         таймер,который генерирует прерывания 18,2 раза в секунду и обеспе-
         чивает механизм для планирования периодических  действий. Утилита
         DOS  PRINT использует таймер для поддержания цикла принтера неза-
         висимо от происходящего в системе.
              Горячие ключи и таймер прерывают работу центрального процес-
         сора,  когда им необходимо его  внимание.  Процессор  обслуживает
         прерывание  и  возвращается  к  прерванной  задаче.  Персональные
         компьютеры имеют специальные аппаратные  средства  для  обработки
         прерываний.  И клавиатура,  и таймер взаимодействуют с ними; если
         Вы хотите использовать клавиатуру и таймер,  то необходимо знать,
         как  аппаратные  средства  и программное обеспечение обрабатывают
         системные прерывания.

                               Аппаратные прерывания

              На уровне аппаратных средств,  поддерживающих  MS-DOS,  есть
         система прерываний от аппаратных средств, каждое из которых ассо-
         циировано  с конкретным устройством. Каждое устройство, ожидающее
         обслуживания процессором,  посылает контроллеру прерываний 8259A,
         который планирует обработку прерываний, запрос на прерывание, или
         IRQ. Каждое  устройство  имеет некоторый приоритет.  Устройство с
         высшим приоритетом первым получает доступ  к  процессору   раньше
         менее  важных устройств.  (Контроллер прерываний 8259A может быть
         запрограммирован и по-другому,  но другие способы обработки менее
         выгодны для  использования).  Когда контроллер прерываний решает,
         что прерывание может быть обработано,  он посылает на  устройство
         сообщение  "подтверждение приема прерывания",  блокирует все ос-
         тальные прерывания и генерирует прерывание.
              В ответ на конкретное аппаратное прерывание,  процессор ищет
         адрес обработки прерывания в таблице векторов  прерываний  (IVT).
         Эта таблица занимает 256 двойных слов (1024 байта) памяти. Каждая
         ее строка содержит адрес подпрограммы обработки прерывания (ISR).
         Процессор  запоминает текущие флаги и программный счетчик (CS:IP)
         и начинает обслуживание прерывания.
              ISR делает  все  необходимое для обслуживания прерываний.  В
         некоторый момент ISR посылает сообщение о конце  прерывания (EOI)
         контроллеру 8259, означающее, что он готов принять запрос на  об-
         служивание следующего прерывания. Контроллер прерываний не  будет
         принимать прерывания от этого или других устройств с более низким
         приоритетом, пока не получит этого сообщения. После того, как ISR
         сделала свою работу,  она выполняет команду IRET, которая восста-
         навливает флаги и первоначальный CS:IP.

                                      - 4-4 -

                               Программные прерывания

              Для процессоров 80х86 механизм программных прерываний обеспе-
         чивает команда INT (обработка прерываний). Процессор одинаково об-
         рабатывает программные и аппаратные прерывания. При исполнении ко-
         манда INT передает управление ISR, специфицированной операндом ко-
         манды.  Например,  команда int 60h вызывает подпрограмму обработки
         прерываний, адрес которой записан в IVT со смещением 180h (4х60h).
         Контроллер прерываний не включается и ISR не посылает EOI контрол-
         леру  прерываний.  DOS  широко  использует программные прерывания.
         Поскольку все обращения к ISR осуществляются через  IVT,  заменить
         подпрограмму обработки прерываний несложно.  Вы будете часто иметь
         повод для модификации IVT при написании резидентных программ.

                               Прерывания от таймера

              PC использует один канал  интегральной  схемы  8253  счетчи-
         ка/таймера для запроса прерываний 18,2 раза в секунду. Контроллер
         8259A в ответ на этот запрос генерирует прерывание  int  8h.  Это
         прерывание  по  таймеру  имеет высший приоритет и будет вытеснять
         любые другие прерывания до тех пор,  пока не будут  заблокированы
         все прерывания командой CLI (очистка прерываний).
               Это прерывание обычно обслуживает программа ROM-BIOS. После
         обновления времени суток и выполнения некоторых  других служебных
         задач,  программа ROM-BIOS выполняет команду int 1Ch.  Программы,
         которые должны выполняться периодически,  могут  установить  свою
         собственную подпрограмму обработки прерывания int 1Сh. По умолча-
         нию подпрограмма обработки прерывания int 1Сh  ROM-BIOS  содержит
         команду IRET.

                                    Клавиатура

              Стандартная клавиатура PC содержит свой собственный  микроп-
         роцессор (Intel 8048 или его эквивалент).  Нажатие или освобожде-
         ние клавиши посылает сигнал IRQ1 контроллеру  прерываний, который
         вызывает подпрограмму обслуживания прерывания int 9 для обработки
         этого запроса.  Приоритет прерываний от клавиатуры  второй  после
         прерываний от таймера.
              ROM (постоянное запоминающее устройство) на  системной плате
         по умолчанию содержит ISR int 9.  Это достаточно сложная програм-
         ма.  Она читает и декодирует считываемый код, отслеживает наличие
         специальных клавиш (Control,Shift,Alt и др.) и преобразует скани-
         руемые коды  во  внутренние.  Каждое нажатие клавиши вырабатывает
         два сканируемых кода - для нажатой и  отпущенной  клавиши.  Выбор
         сканируемого  кода  зависит от информации о состоянии клавиатуры.
         Например,  нажатие клавиши A производит сканируемый код 61h  (код
         ASCII строчной буквы а). Если при нажатии клавиши A нажата управ-
         ляющая клавиша, сканируемый код трансформируется в 01h (код ASCII
         для Control-A).  Если при нажатии клавиши A нажата клавиша Shift,
         сканируемый код получается 41h (ASCII для заглавной буквы A).
              В результате  нажатия таких клавиш как Shift и Alt,int 9 ISR
         обновляет байт состояния клавиатуры внутри сегмента данных BIOS и
         обращается к IRET. Сегмент данных BIOS начинается с параграфа 40h
         и содержит множество динамических переменных,  используемых  раз-
         личными подпрограммами ROM-BIOS. Листинг 4-1 описывает часть этой
         области данных.

                                      - 4-5 -

                         Листинг 4-1. Сегмент данных BIOS
         ----------------------------------------------------------------

         KB_M_RShift     EQU   01h   ; установка правой клавиши сдвига
         KB_M_LShift     EQU   02h   ; установка левой клавиши сдвига
         KB_M_Control    EQU   04h   ; установка управляющей клавиши
         KB_M_Alt        EQU   08h   ; установка клавиши "Alt"
         KB_M_Scroll     EQU   10h   ; нажатие клавиши "Scroll Lock"
         KB_M_Num        EQU   20h   ; нажатие клавиши "Num Lock"
         KB_M_Caps       EQU   40h   ; нажатие клавиши "Caps Lock"
         KB_M_InsState   EQU   80h   ; режим вставки

         KB_C_BufSize    EQU   10h   ; размер буфера клавиатуры

         BIOS            SEGMENT     at 40h
                         ORG   17h   ; не существенно для др. данных BIOS
         KB_B_Flag       DB    0     ; флаг состояния клавиатуры
                         ORG   1ah   ; не существенно для 18h и 19h
         KB_W_BufHead    DW    0     ; начало буфера клавиатуры
         KB_W_BufTail    DW    0     ; оставшаяся часть буфера клавиатуры
         KB_T_Buffer     DW    KB_C_BufSize DUP(0)
         BIOS            ENDS
         ----------------------------------------------------------------

              Определенные комбинации  клавиш  имеют специальные значения.
         Подпрограмма обработки прерываний от клавиатуры выполняет команду
         int 1Bh, когда она видит сканируемый код, соответствующий клавише
         прерывания.  По умолчанию ISR int 1Bh содержит  IRET,  но  обычно
         драйвер консоли устанавливает свою собственною ISR 1Bh,  что дает
         ему возможность обрабатывать его прерывания  специальным образом.
         (Этот вопрос обсуждается в главе об обработке прерываний).
              В конечном счете  страшная  Cntrl-Alt-Del  выдает  int  19h.
         Дальнейшее  обсуждение  int 19h и комбинации клавиш Cntrl-Alt-Del
         необязательно.
              Если код  клавиши не имеет специального значения,  ISR int 9
         сохраняет его в буфере клавиатуры.  Этот буфер начинается со сме-
         щения  1h внутри сегмента данных BIOS и представляет собой цирку-
         лярный буфер из 16 слов. Смещения 1Ah и 1h в этом сегменте указы-
         вают, соответственно, на начало и конец буфера. Если буфер полон,
         ISR int 9 выдает звуковой сигнал и отвергает символ;  в противном
         случае символ вставляется в конец буфера.
              Длина каждого элемента буфера 2 байта; его формат зависит от
         того, как ISR int 9 интерпретирует нажатие клавиши.
              С помощью определенных комбинаций клавиш (например, Alt плюс
         буква или цифра) и специальных клавиш  (например,  функциональных
         клавиш)  воспроизводятся  символы  расширенного ASCII;  остальные
         клавиши воспроизводят обычный ASCII. Нулевой байт записи подпрог-
         раммы  обработки прерываний int 9 содержит числовой идентификатор
         для расширенных символов ASCII,  запись кода символа ASCII и ска-
         нируемый код для всех других.  Программное  обеспечение  доступно
         аппаратным средствам клавиатуры через ROM-BIOS. Int 16h позволяет
         удалять символ из буфера клавиатуры,  взглянув на первый символ в
         буфере,  и изменять статус клавиатуры. Фактически, любой доступ к
         клавиатуре осуществляется через int 16h.  Любой  драйвер  консоли
         использует  для  ввода  символов  и  изменения статуса клавиатуры
         int 16h.
                              Аппаратура отображения

              Имеется большое  количество  различных  дисплеев,  доступных
         компьютерам семейства PC.  Одноцветный дисплейный адаптер (MDA) и
         цветной графический адаптер (CGA) наиболее общеизвестны.  Некото-
         рые  другие  аппаратные  средства  могут заменять один или оба из
         них, предоставляя дополнительные возможности (больше цветов, луч-
         шая разрешающая способность и т. д.). Это описание ограничивается
         MDA и CGA.
              Аппаратные средства отображения PC имеют аналоговые и цифро-
         вые  компоненты.  Экран и ассоциированная с ним логика управления
         составляют аналоговую часть. Поверхность экрана покрыта фосфором,
         который светится, когда об нее ударяется пучок электронов. Анало-
         говые схемы управления пересекают пучком электронов экран и опус-
         кают его вниз. Другие части схемы включают и выключают его.
              Этот процесс начинается с верхнего левого угла экрана. Пучок
         передвигается  горизонтально через экран слева направо.  Когда он
         достигнет правой стороны экрана, управляющая электроника выключа-
         ет его и возвращает его в левый угол и вниз на одну позицию. Вре-
         мя,  когда пучок выключен,  известно, как горизонтальный интервал
         гашения. Процесс продолжается до тех пор, пока пучок не пересечет
         нижнюю строку экрана. Когда пучок электронов достигнет низа экра-
         на,  схема  управления выключает его и возвращает в верхний левый
         угол экрана для повторения всего процесса. Время, необходимое для
         этого,  называется вертикальным интервалом возвращения.  Горизон-
         тальный интервал гашения и вертикальный интервал возвращения важ-
         ны для поддержки CGA.
              При горизонтальном  движении пучка электронов вправо изобра-
         жение экрана,  сохраняемое в памяти, содержит необходимые сигналы
         для включения и выключения пучка электронов и управления им.  Ба-
         зовый адрес этой памяти изменяется в зависимости от типа  адапте-
         ра.  Память экрана MDA начинается с B000h до 0000h,  память CGA с
         B800h до 0000h. И CPU, и контроллер CRT имеют доступ к этой памя-
         ти.
              Некоторые любители электроники делают  эту  подготовительную
         работу сами, но, при желании, вы можете считать их и писать в па-
         мять экрана, не слишком беспокоясь о том, что  делает  контроллер
         CRT 6845. Контроллер CRT 6845 является интегральной схемой общего
         назначения,  которая поддерживает несколько различных  мониторов.
         Она  имеет статус регистра,  который содержит информацию о циклах
         восстановления; другие регистры управляют скоростью сканирования,
         позицией  курсора,  способом  управления  курсором и отображением
         страниц.
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                                  ПРЕДУПРЕЖДЕНИЕ:
              Вы должны быть очень внимательны при  программировании 6845.
         Определенные регистры содержат критические значения, которые, ес-
         ли они не установлены должным образом,  могут разрушить Ваш мони-
         тор.  Более  полное  описание  смотри  в  "IBM Hardware Technical
         Reference Manual".
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                                     МDA и CGA

              Между  MDA и CGA существуют некоторые  аппаратные  различия.

                                      - 4-7 -
         MDA функционирует достаточно устойчиво,  так что память отображе-
         ния доступна CPU в любое время, даже когда сканируемая строка ак-
         тивна. Попытка  доступа  к  графической памяти CGA ,  если она не
         происходит во время вертикального  возврата,  производит  "снег".
         Наиболее  медленный  процессор  IBM (CPU 8088 c тактовой частотой
         4.77 мегагерц) может передать только 1 байт во время периода  го-
         ризонтального возвращения и приблизительно 100 байт во время вер-
         тикального возвращения. И CGA, и MDA предусматривают вертикальный
         статус возвращения,  но только CGA имеет горизонтальное возвраще-
         ние.
              Кроме этого,  между CGA и MDA существуют функциональные раз-
         личия.  MDA  может отображать только текст;  CGA может отображать
         текст и  изображения.  В  текстовом режиме для отображения одного
         символа оба адаптера используют 2 байта  памяти  экрана.  Младший
         байт содержит отображаемый символ и старший байт описывает символ
         -атрибут (яркий,  мерцающий,  цветной,  подчеркнутый  и  т.  д.).
         Хранение графических  данных несколько более сложно.  Подробности
         см. в "IBM Hardware Technical Reference Manual".

                            Занесение в память дисплея

              Память дисплея отображается в адресное пространство PC. Лис-
         тинг 4-2 показывает, как несложно записать в память MDA.

                      Листинг 4-2. Прямая запись в память MDA
         -----------------------------------------------------------------
         ; Запись приветствия на экране в начале изображения (0,0).
         ;Семерка, следующая  за каждой буквой является атрибутом отоб-
         ;ражения. Значение 7 описывает нормальный режим (буквы на
         ;темном фоне, с обычной интенсивностью)

         Hello        DB    'H',7,'e',7,'l',7,'l',7,'o',7
         HelloLength  EQU   $-Hello

                      mov   ax,0b000h
                      mov   es,ax          ; es <== адрес MDA
                      xor   di,di          ; di <== смещение памяти экрана
                      mov si,OFFSET Hello  ; si <== строка для записи
                      mov cx,HelloLength/2 ; cx <== слово для записи
                      rep   movsw          ; запись
         -----------------------------------------------------------------

              Запись в CGA имеет некоторые особенности.  Приведенная прог-
         рамма будет выполнятся на CGA (если базовый адрес экрана изменить
         на 0B00h), но это будет причиной появления "снега" на экране. Так
         как память адаптера имеет два порта,  она может быть доступна CPU
         и  процессору  дисплея  (контроллер  6845  CRT  фирмы  Motorola).
         "Cнег" является  результатом двойного обращения к памяти  -  про-
         цессор и контроллер пытаются получить доступ к памяти одновремен-
         но. Доступ к памяти дисплея во время циклов возвращения уничтожа-
         ет этот неприятный эффект.
              MDA и многие CGA работают достаточно быстро, чтобы ограниче-
         ние  в  использовании только интервалов возвращения были бы несу-
         щественными. С  CGA IBM Вы можете избавиться от "снега", выключая
         изображение во время обновления экрана (что хуже, чем "cнег"),или
         используя  синхронизацию с сигналами возвращения. Листинг 4-3 ил-
         люстрирует,как избежать "снег" путем синхронизации с горизонталь-

                                      - 4-8 -
         ным сигналом возвращения с использованием младшего значащего бита
         регистра состояния 6845 по адресу 03DAh.

                      Листинг 4-3. Прямая запись в память CGA
         -----------------------------------------------------------------
         ; Запись приветствия на экране в начале изображения (0,0).
         ;Семерка, следующая  за каждой буквой является атрибутом отоб-
         ;ражения. Значение 7 описывает нормальный режим (запись букв
         ;на темном  фоне,  с обычной интенсивностью).  Предполагается
         ;текстовый режим  CGA.

         Hello        DB    'H',7,'e',7,'l',7,'l',7,'o',7
         HelloLength  EQU   $-Hello

         HRetrace     EQU   1

                      mov   dx,3dah         ; dx <== регистр состояния CGA
                      mov   ax,0b800h
                      mov   es,ax           ; es <== память адаптера CGA
                      xor   di,di           ; di <== смещение памяти экрана
                      mov   si,OFFSET Hello ; si <== строка для записи
                      mov   cx,HelloLength/2; cx <== слова для записи
         _nextbyte:
         _sync:       in    al,dx           ; al <== состояние 6845
                      test  al,HRetrace     ; горизонтальное возвращение?
                      jz    _sync           ; если z - еще нет
                      stosb                 ; запись 1 байта в HRetrace
                      loop _nextbyte        ; ожидание следующего HRetrace
         -----------------------------------------------------------------

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

                             Видео-поддержка ROM-BIOS

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

                                Подмена прерывания

              Процесс изменения строки IVT (таблицы  векторов  прерываний)
         известен как подмена прерывания. Резидентные программы запускают-
         ся от прерываний при нажатии горячих ключей. Те, которые выполня-
         ются периодически,  зависят от прерываний от таймера.  Обычно для
         управления обработкой запроса DOS и состоянием аппаратных средств
         и для размещения самих предварительно загруженных копий TSR изме-
         няют IVТ.  Для некоторых команд DOS (функций int 21h)  прерывания

                                      - 4-9 -
         от таймера дают непредсказуемый результат.
              Для подмены прерывания код инициализации TSR считывает стро-
         ку IVT, запоминает ее содержимое в области данных и вставляет но-
         вый адрес в таблицу IVT.  При получении следующего прерывания уп-
         равление  будет передаваться новой программе обработки прерываний
         (ISR).  Новый код ISR будет обычно вызывать  сначала оригинальную
         ISR. Когда старая ISR выполнится, ее команда IRET передаст управ-
         ление Вашему коду, который для передачи управления программе вво-
         дит собственную команду IRET ,  которая передает управление прог-
         рамме, которая первоначально вызвала прерывание.
              DOS обеспечивает  два пути для перехвата вектора прерывания.
         Для нахождения содержимого специфицированной строки IVT  помещает
         номер  ее  прерывания в регистр AL,  значение 35h в регистр AH и
         выполняет команду 21h.  BIOS обеспечивает возвращение содержимого
         строки IVT в пару регистров ES:BX.
              После записи этого  значения,  можно  модифицировать  строку
         IVT.  В DS:DX загружается адрес новой ISR, в регистре AL задается
         номер вектора прерывания, в регистр AH помещается 25h, и выполня-
         ется  команда  21h.  Листинг 4-4 иллюстрирует использование этого
         способа подмены прерывания от таймера.

                Листинг 4-4. Подмена прерывания от таймера Int 1ch
         ----------------------------------------------------------------

         OldInt1c   DD   0

                    mov     ax,351ch              ; получение int 1c
                    int     21h
                    mov     WORD PTR OldInt1c,bx  ; сохранение его
                    mov     WORD PTR OldInt1c+2,es; сохранение ds
                    push    ds
                    mov     ax,cs
                    mov     ds,ax
                    mov     ds,OFFSET NewInt1c     ; ds:dx <== новая isr
                    mov     ax,251ch               ; установить новую isr
                    int     21h
                    pop     ds                     ; восстановление ds
         ;          ...                            ; все, что угодно
         NewInt1c   PROC    FAR
                    pushf                    ; моделирование флагов стека
                    call    cs:OldInt1c            ; прерывание
         ;          ...                            ; все, что угодно
                    iret
         NewInt1c   ENDP
         ----------------------------------------------------------------

              Специальные действия, включенные в новую ISR, зависят от то-
         го,  какую  строку IVT вы меняете и что вы предпринимаете для вы-
         полнения этой замены.  Заметим,  что новая ISR "сцеплена" со ста-
         рой. Эта техника общепринята.  Последовательность pushf/call под-
         меняет команду INT.  Заметим,  что call должен быть межсегментным
         (дальним) вызовом,  потому, что OldInt1c является двойным словом.

                              Создание горячего ключа

              Назначение горячего ключа для TSR налагает некоторые особые
         требования на написание  программы.  Горячий ключ инициирует TSR

                                      - 4-10 -
         без передачи  сигнала  нажатия клавиши программе переднего плана.
         При обычном подходе каждое нажатие клавиши перед  считыванием его
         программой  переднего плана анализируется.  Для просмотра ввода в
         буфер клавиатуры Вы можете перехватить прерывание 16h или Вы  мо-
         жете опрашивать буфер клавиатуры, используя прерывания от таймера
         (int 1ch),  или,  наконец,  Вы можете управлять содержимым буфера
         клавиатуры  при перехвате прерывания 9.  Довольно часто Вы можете
         посчитать полезным назначить горячие ключи, влияющие на состояние
         клавиатуры, но не добавляющие символов в буфер клавиатуры. Каждый
         из этих подходов имеет определенные преимущества и  проблемы.  Вы
         имеете возможность решить,  какая техника лучше для Вашего прило-
         жения.

                                  Подмена Int 16h

              Простейшим путем создания горячего  ключа  является  подмена
         прерывания int 16h.  Большинство хорошо работающих приложений ис-
         пользуют это прерывание для ввода с клавиатуры.  Установка  собс-
         твенного прерывания int 16h ISR позволит Вам анализировать каждый
         символ и отклонять любые горячие ключи. Листинг 4-5 демонстрирует
         типичную замену для int 16h ISR.


               Листинг 4-5. Замена прерывания int 16h для просмотра
                                используемых клавиш
         ----------------------------------------------------------------

        OldInt16     DD    0         ; сохранение кода инициализации
                                     ; адрес первоначальной isr
        Hotkey       DW   (?)        ; определение нажатой клавиши

        NewInt16     PROC  FAR
                     cmp   ah,1       ; проверка функции
                     jg    DoShift    ; если g -- сдвиг
                     jl    DoRead     ; ah=0 ==> чтение
        DoStatus:                     ; ah=1 ==> проверка состояния
                     pushf            ; моделирование  int 16
                     call  cs:OldInt16 ; передача запроса BIOS
                     pushf            ; сохранение флагов
                     cmp   ax,HotKey  ; найдена нажатая клавиша?
                     jnz   Done1      ; нет
                     xor   ax,ax      ; ah <== 0 (запрос чтения)
                     call  cs:OldInt16  ; удаление нажатой клавиши
                     call  ActivateTSR  ;  нажатая клавиша вызывает TSR
                     mov   ah=1       ; ah <== 1 (запрос состояния)
                     jmp   SHORT DoStatus ; повторение запроса
        DoRead:
                     pushf            ; моделирование int 16h
                     call  cs:OldInt16
                     cmp   ax,HotKey  ; найдена нажатая клавиша?
                     jnz   Done0      ; если nz - нет
                     call  ActivateTSR ; нажатая клавиша вызывает TSR
                     xor   ah,ah      ; ah <== 0 (запрос чтения)
                     jmp   SHORT DoRead ; повторение запроса
        DoShift:                        ; передача запроса
                     jmp   cs:OldInt16  ; старая ISR. Сброс
        Done0:                        ; ax имеет не используемые флаги

                                      - 4-11 -
                     iret                 ; возврат для вызова
        Done1:                            ; ax имеет символ
                     popf           ; восстановление флагов int 16h
                     ret    2       ; отбрасывание флагов, смоделированных
                                    ; командой int и возврат

        NewInt16     ENDP
         ----------------------------------------------------------------

              Новая  int  16h ISR проверяет результаты каждого считывания
         (AH=0) и  запроса  о состоянии буфера (AH=1),  но не осуществляет
         проверку состояния shift запросов (AH=2). Если код ROM-BIOS  воз-
         вращает горячий ключ,  новая ISR удаляет код  клавиши  из  буфера
         клавиатуры,  инициирует TSR, и повторяет запрос. Если только пер-
         вый символ буфера клавиатуры оказывается горячим ключом ,  ISR не
         повторяет  запрос.  Этот пример сделан при упрощающем предположе-
         нии,  что реактивация  резидентной  программы  будет  безопасной.
         (Подробное  обсуждение  этой  темы смотри в разделе "Реактивация,
         архитектура DOS и сервис". Следовательно, коды в листинге 4-5 яв-
         ляются только моделью, и не совсем корректны).
              Ограничение для этого метода заключается в том,  что горячий
         ключ можно выявить только один  раз,  когда  программа  переднего
         плана задает  считывание.  Если  эта программа производит большой
         объем вычислений, то между временем нажатия клавиши и ответом TSR
         задержка может быть большая.

              Опрос буфера клавиатуры прерыванием от таймера Int 1Ch

              Вы можете обеспечить постоянный контроль клавиатуры подменой
         прерывания от таймера и проверкой буфера клавиатуры с помощью Ва-
         шей программы обработки прерываний от таймера. Листинг 4-6 прове-
         ряет используемую клавишу при каждом прерывании от  таймера. Если
         первый код клавиши в буфере клавиатуры соответствует используемой
         клавише, новая TSR удаляет код клавиши и активизирует TSR. В про-
         тивном случае новая ISR обращается к первоначальной программе об-
         работки прерываний от таймера.

                  Листинг 4-6.  Использование прерывания Int 1ch
                               для опроса клавиатуры
         ----------------------------------------------------------------

         HotKey          DW    (?)       ; определение нажатой клавиши
                                          ; заметим, что ascii
                                          ; не может быть расширена
         OldInt1c        DD    0          ; запоминание старого адреса ISR

         NewInt1c        PROC  FAR        ; новый таймер isr
                         puch  ax         ; необходимо для int 16h
                         xor   al,al      ; xor быстрее очищает al,чем
                         inc   al         ; mov al,1
                         int   16h        ; проверка буфера клавиатуры
                         jz    NoHotKey   ; если z - буфер пустой
                         cmp   ax,HotKey  ; не пустой -- нажатая клавиша?
                         jnz   NoHotKey   ; если nz -- клавиша не нажата
                         xor   al,al      ; al <== запрос чтения
                         int   16h        ; удаление нажатой клавиши
                         call  ActivateTSR; обращение к TSR

                                      - 4-12 -
         NoHotKey        pop   ax         ; восстановление ax
                         jmp   cs:OldInt1ch; передача отметки времени
         NewInt1c        ENDP
         ----------------------------------------------------------------

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

                                 Ловушка для Int 9

              Другим обращением  к  управлению клавиатурой является Int 9.
         При нажатии или освобождении клавиши аппаратные средства  генери-
         руют прерывание Int 9. Новая ISR Int 9 вызывает ISR ROM клавиату-
         ры и использует Int 16h для просмотра первого символа буфера кла-
         виатуры. Недостатком  этого обращения  является то,  что непустой
         буфер клавиатуры скрывает горячий ключ.  Если Вы  можете  обеспе-
         чить, что ни одна TSR не будет впоследствии загружена в буфер, Вы
         можете использовать этот метод, сканируя буфер целиком при каждом
         нажатии клавиши.
              TSR, которые расширяют буфер  клавиатуры,  используются  до-
         вольно широко. Они замещают ISR int 9 и int 16h. Их код int 9 вы-
         зывает старую ISR int 9 для обслуживания прерывания от клавиатуры
         и  затем вызывает старую ISR int 16h для просмотра буфера клавиа-
         туры.  Новая ISR int 9 запоминает эти символы в своем собственном
         буфере.  Замещенная  ISR  int 16h удаляет символы из этого нового
         буфера.
              TSR, которые  переопределяют или привязывают к клавишам мак-
         роопределения,  также используют этот метод. Если Ваша TSR загру-
         жает перед собой другую TSR, которая пересылает буфер клавиатуры,
         Ваша TSR всегда будет находить буфер пустым. Это не лучший способ
         написания  TSR,  корректность  работы которого зависит от порядка
         загрузки.

                         Управление состоянием клавиатуры

              Альтернативой для проверки буфера клавиатуры является наблю-
         дение за байтом состояния клавиатуры.  Этот метод исключает необ-
         ходимость знать местонахождение ROM-BIOS  буфера  клавиатуры,  но
         требует, чтобы пользователь выбрал комбинацию клавиш, которая при
         ее нажатии изменяет состояние клавиатуры (т. е. Alt-Shift, напри-
         мер).  Этот метод будет работать до тех пор, пока любая загружен-
         ная после нее TSR не изменит байт состояния клавиатуры.  Так  как
         состояние  клавиатуры влияет на обработку сканируемого кода, этот
         способ будет работать, пока TSR не будет изменена.
              Листинг 4-7 представляет замену для ISR ROM-BIOS клавиатуры.
         Некоторые вещи, которые делает эта программа, могут прямо  сейчас
         показаться слегка неверными, потому что процесс распознавания го-
         рячего ключа возлагается на сервисную подпрограмму обработки пре-
         рываний. Ниже Вы увидите,что Вы не можете обеспечить безопасность
         прерывания некоторых команд DOS'а.  Одной из претензий написанной

                                      - 4-13 -
         TSR является снятие этих ограничений.
              В этом примере новая ISR выполняется каждый раз, когда горя-
         чий ключ нажат или опущен. Сначала она вызывает старую ISR клави-
         атуры для считывания и обработки  сканируемого  кода  клавиатуры.
         Новая TSR проверяет переменную PgmState,  поддерживаемую TSR, для
         определения, является ли TSR программой переднего плана. Если TSR
         выполняется  не  в  переднем плане и ISR распознает горячий ключ,
         она попытается вызвать TSR в передний план. Если TSR в данный мо-
         мент работает в переднем плане, то прерывание дальнейшей обработ-
         ки не потребует.
              Если биты состояния клавиатуры,соответствующие горячему клю-
         чу,  установлены,  ISR добавляет флаг ожидания (Popup Pending)  и
         проверяет возможность безопасного вызова TSR в передний план.
              Механизм этого процесса описывает раздел "Реактивация, архи-
         тектура DOS и сервис". Если безопасность обеспечена, то чтобы ре-
         активировать TSR,  ISR   вызывает  BKGResume.  DOSSafe  добавляет
         BusyFlag, предупреждающий повторный запуск TSR; перед возвращением
         к прерванной программе ISR должна эту переменную восстановить.


                     Листинг 4-7. Пример замены ISR клавиатуры
         ----------------------------------------------------------------

       FGCombo         EQU   KB_M_Alt OR KB_M_LShift

       BKG_C_FG        EQU   1
       BKG_C_BG        EQU   2

       BIOS      SEGMENT     at 40h
                 ORG   17h
       KB_B_Flag       DB    0
       BIOS      ENDS

       _text     SEGMENT     BYTE PUBLIC  'code'-
       PgmState        DB    0
       BusyFlag        DB    -1        ; запрет прерывания
                                       ; нереентерабельная часть программы
       OldInt9         DD    0         ; сохранение первоначальной ISR int9
       PopupPending    DB    0         ; приращение, если запрос не
                                       ; может быть обслужен

                       ASSUME ds:NOTHING
       Int9ISR         PROC  FAR
       NewInt9:
                       pushf            ;;; моделирование прерывания
                       call  cs:OldInt9 ;;; вызов первоначальной ISR
                       cmp   cs:PgmState,BKG_C_BG ;;; фоновая программа?
                       jz    i9_0       ;;; если z - да
                       iret             ;;; не выбирать из стека, если нет
      i9_0             pushr <ax,ds>    ;;; доступ к B_Flag
                       mov   ax,SEG BIOS
                       mov   ds,ax
                       ASSUME ds:BIOS
                       mov   al,KB_B_Flag;;; al <== текущие флаги KB
                       and   al,FGCombo  ;;; маска всех ненужных битов
                       cmp   al,FGCombo  ;;; запрошена выборка из стека?
                       popr  <ds,ax>

                                      - 4-14 -
                       ASSUME ds:NOTHING
                       jnz Int9Exit1   ;;; если NZ - нет запроса
                                       ;;; выборки из стека
                       inc cs:PopupPending ;;; выборка из стека запрошена
                       call  DOSSafeCheck ;;; можно ее делать?
                       jc    Int9Exit0   ;;; если с - нет
                       call BKGResume    ; вызов приоритетной программы
       Int9Exit0:
                       dec   cs:BusyFlag ; выпуск программы
       Int9Exit1:
                       iret              ; отмена прерывания
       Int9ISR         ENDP
       _text           ENDS
         ----------------------------------------------------------------

                        Альтернатива для перехвата Int 1Сh

              Важно заметить, что ISR Int 1Сh вложена в ISR Int 8, так как
         прерывание по времени имеет высший приоритет;  никакие прерывания
         не обслуживаются,  пока контроллер прерываний не получит EOI (ко-
         нец прерывания). Любые команды, которые зависят от прерываний, не
         будут работать.  Другой потенциальной проблемой является то,  что
         DOS будет терять такты таймера, если он будет слишком долго путе-
         шествовать по цепочке int 1Сh.  PRINT.COM решает эту проблему пе-
         ресылкой EOI в свой int 1Сh ISR.
            Альтернативной стратегией является перехват int 8. Новая int 8
         ISR сразу вызывает старую ISR,  которая посылает EOI  контроллеру
         прерываний перед возвращением.  ISR,  приведенная на листинге 4-8
         работает вместе с приведенной на листинге 4-7.  Если горячий ключ
         не обработан, или если с последней активации прошла 1 секунда, то
         для реактивации TSRint 8 ISR вызывает BKGResume.


            Листинг 4-8. Пример замены прерывания по времени ISR int 8
         ----------------------------------------------------------------

        OldInt8       DD    0     ; сохранение кода инициализации
                                  ; начальный адрес ISR int 8
        BusyFlag      DB    -1    ; запрет прерывания
                                  ; нереентерабельная  секция
                                  ; программы
        PopupPending  DB    0     ; не 0, если встретилась нажатая клавиша
        Ticks         DB    18    ; выполняется один раз в секунду

        Int8ISR       PROC  FAR
        NewInt8:
                      pushf            ;;; моделирование прерывания
                      call  cs:OldInt8 ;;; посылка кода ROM
                      cli              ;;; не является необходимым
                      cmp   cs:PopPending,0  ;;; ожидание запроса выборки?
                      jnz   i8_0       ;;; если не 0 - да
                      cmp   cs:Ticks,0 ;;; счетчик тактов = 0?
                      jz    i8_0       ;;; если 0 - да
                      dec   cs:Ticks   ;;; иначе - уменьшение его
                      jnz   Int9Exit1  ;;; если еще не 0 - продолжение
        i8_0          call  DOSSafeCheck ;;; OS не испорчена?
                      jc    Int8Exit0   ;;; если c - нет

                                      - 4-15 -
                                      ;;; заметим, что отметка времени
                                      ;;; остается на 0, попытаемся
                                      ;;; сохранить для передачи
                                      ;;; каждую отметку
                      call  BKGResume ; передача фоновой программе
                      mov   cs:Ticks,18 ; сброс счетчика
        Int8Exit0:
                      dec   cs:BusyFlag ; закрыть
        Int8Exit1:
                      iret            ; возврат
        Int8ISR       ENDP
         ----------------------------------------------------------------

                         Управление отображением на экране

              Учитывая ранее   приведенные  ограничения  в  сервисе  видео
         ROM-BIOS,  для непосредственного управлении аппаратурой отображе-
         ния на экран часто требуется TSR. Прямое чтение   и запись на эк-
         ран ускоряют процесс переключения дисплеев,  когда  горячий  ключ
         активизирует TSR, устраняет проблему, связанную с изменением меж-
         ду текстовым и и графическим  режимами,  может  уменьшить  прямой
         доступ к контроллеру 6845 CRT.

        ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                                  ПРЕДУПРЕЖДЕНИЕ:

              Прямой доступ  к  аппаратуре  отображения может быть опасен.
              Ошибка при такой обработке может разрушить Ваш монитор.
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

              Перед тем, как попытаться программировать дисплей, надо себе
         представлять,  как он работает. Нижеследующее обсуждение является
         просто обзором;  подробнее см.  в "Hardware  Technical  Reference
         Manual".
              Есть два способа для изменения содержимого экрана. Один спо-
         соб  заключается  в поддержке двух буферов:  один из них содержит
         образ экрана TSR,  а другой - образ экрана приложений DOS. Второй
         способ замещает видео-память одного из этих буферов; это сохраня-
         ет некоторую память за счет незначительного замедления ответа.
              Листинг 4-9  демонстрирует способ дублирования буферов.  При
         нажатии горячего ключа текущий экран копируется в буфер  приложе-
         ний DOS,  и  затем  содержимое  буфера  TSR перемещается в память
         дисплея.  Вы можете в этот момент пересылать больший блок данных,
         так  что  используйте  команду пересылки строк.  Расчет временных
         циклов предполагает, что эта процедура займет около 21 мс для вы-
         полнения на 8088 процессоре с частотой 4.77 MHz.  Реальные замеры
         дают около 29 мс.  Разница частично вызвана погрешностями  метода
         расчета временных циклов; остальное относится за счет одновремен-
         ного обращения к неразделяемому ресурсу. Замер времени был сделан
         при включенном дисплее - в худшем варианте.


                        Листинг 4-9.  Переключение  экранов
                           с использованием двух буферов
         ----------------------------------------------------------------

         _text           SEGMENT    WORD PUBLIC 'CODE'

                                      - 4-16 -
                         ASSUME cs:_text, ds:_text, es:_text
         VideoSEG        DW    0b000h
         DOSBuffer       DW    25*80           DUP   (0)
         TSRBuffer       DW    25*80           DUP   (720h)

         Switct          PROC  NEAR
                         cld                ; флаг направления <== UP
                         lea   di,DOSBuffer ; di <== смещение буфера
                         mov   ax,cs
                         mov   es,ax        ; es:di <== буфер DOS
                         xor   si,si        ; si <== смещение видео
                         mov   ds,VideoSEG  ; ds:si <== память видео
                         mov   cx,25*80     ; cx <== слова на отображении
                         rep   movsw        ; DOSBuffer <== память видео
                         mov   ds,ax
                         lea   si,TSRBuffer ; ds:si <== буфер TSR
                         mov   es,VideoSEG  ;
                         xor   di,di        ; es:di <== видео память
                         mov   cx,25*80     ; cx <== слова на отображении
                         rep   movsw        ; память видео <== буфер TSR
                         ret
          Switch         ENDP
         ----------------------------------------------------------------

                .Следующий листинг использует только один буфер.  Использова-
         ние отдельного буфера  замедляет  последовательность  mov/xchg  и
         требует для изменения экрана при включенном дисплее приблизитель-
         но 45 мс. Такая производительность вполне приемлема. Заметим, что
         выравнивание буфера по границе параграфа обходится в дополнитель-
         ную команду add,  но это изменение не  влияет  на  производитель-
         ность.


                        Листинг 4-10.  Переключение  экрана
                        с использованием отдельного буфера
         ----------------------------------------------------------------

         _text           SEGMENT        WORD PUBLIC 'CODE'
                         ASSUME  cs:_text, ds:_text, es:_text
         VideoSEG        DW    0b000h
         TSRBuffer       DW    25*80       DUP   (720h)

         Switch          PROC NEAR
                         cld          ; проверка, что мы подвинулись
                         lea   si,TSRBuffer ; si <== смещение буфера TSR
                         xor   di,di  ; di <== смещение видеопамяти
                         mov   bx,2   ; bx <== размер сдвига
                         mov   es,VideoSEG ; ds:si <== видеопамять
                         mov   cx,25*80 ; cx <== слова на экране
         _nb             mov   ax,[si]  ; ax <== слово из буфера TSR
                         xchg  ax,es:[di] ; видеопамять <== буфер TSR
                                          ; ax <== слово из видеопамяти
                         mov   [si],ax    ; буфер TSR <== видеопамять
                         аdd   si,bx
                         add   di,bx
                         loop  _nd
                         ret

                                      - 4-17 -
         Switch          ENDP
         ----------------------------------------------------------------

                                Работа в среде DOS

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

                         Структуры данных ввода/вывода DOS

              DOS поддерживает много важных для TSR структур. Некоторые из
         них являются общими для всех резидентных программ.  Например, DOS
         поддерживает две системные таблицы файлов,  одна для  обработчика
         доступа, другая  для  функционирования  блока  управления  файлом
         (FCB). Все программы имеют доступ к одним и тем же системным таб-
         лицам.  Другие структуры данных индивидуальны для каждой програм-
         мы.  Например,  каждая  программа имеет свой сегмент программного
         префикса (PSP).
              Когда DOS загружает программу, она записывает PSP этой прог-
         раммы в  общие переменные (В DOS 3.10 эти переменные размещены со
         смещением 02DEh в сегменте DOS).  Программа,  чей PSP  записан  в
         сегменте DOS, становится текущей. Как только IBMBIO загрузила яд-
         ро системы,  имеется всегда одна и только одна текущая программа.
              Когда программа делает запрос на  ввод/вывод,  она  передает
         DOS описатель или блок управления файлом. Для обработки описателя
         файла DOS должен найти структуру данных,  известную  как  рабочая
         таблица  файлов (JFT).  Каждый PSP содержит адрес JFT на смещении
         34h (Листинг 4-5 "Структура PSP"). Для нахождения текущей JFT DOS
         проcматривает PSP текущей программ. Обычно JFT начинается со сме-
         щения 18h PSP (т.е.  адрес JFT указывает другое  смещение  внутри
         PSP).  Для получения номера системного файла (SFN),  который,   в
         свою очередь,  является индексом в системной таблице файлов,  DOS
         использует описатель как индекс в JFT.  Одно из неописанных полей
         внутри FCB содержит номер системного файла(FSN); этот SFN являет-
         ся  индексом системной таблицы файлов FCB.  Эта системная таблица
         указывает DOS, как найти устройство.

                                 "Список списков"

              DOS записывает адреса системных таблиц как описателей  так и
         FCB  в  структуру  данных,  известную  как "список списков".  Эта
         структура данных содержит и другую важную информацию.  Вашему TSR
         может понадобиться  просмотреть содержимое этого списка или неко-
         торые структуры данных,  на которые он указывает.  Недокументиро-
         ванная  функция AH=52h прерывания int 21h возвращает адрес списка
         списков в паре регистров ES:BX.  Фрагмент программы,  приведенной
         на листинге 4-11 показывает, как найти этот список.


                                      - 4-18 -
                        Листинг 4-11. Поиск списка списков
         ----------------------------------------------------------------

         ListAddr        DW    0,0

                         mov   ah,52h ; запрос DOS, где он размешен
                         int   21h    ; (недокументированная функция)
                         mov   ListAddr,bx ; адрес возвращается в ex:bx
                         mov   ListAddr+2,es
         ----------------------------------------------------------------

              Короче говоря,  имеются вполне определенные функции для раз-
         ных входов в список списков. Блочное устройство (обычно диск) за-
         писывает  информацию о структуре файловой системы в блок управле-
         ния устройством (DCB).  Данные DCB обычно включают размер  диска,
         количество  входов  в корневой директорий,  количество FAT и т.д.
         DOS записывает адрес системных часов в качестве оптимизатора про-
         изводительности. Вдобавок  к обработке запросов о времени и дате,
         DOS записывает временные метки при каждой записи FCB и затем  за-
         писывает время самого последнего обращения  к  записи  описателя.
         DOS  использует сохраненный адрес клавиатуры для проверки сигнала
         break и для сообщения об ошибках "деление на нуль". DOS предпола-
         гает,   что клавиатура имеет ISR int 1Bh,  так что ISR клавиатуры
         может обрабатывать break немедленно.  Для операций  над  блочными
         устройствами DOS использует текущий директорий.  DOS поддерживает
         список используемых кэш-блоков для  обработки  запросов  на  чте-
         ние/запись отдельных блоков и для обращения к блокам директория и
         FAT.  Длина каждого кэш-блока указана в DOS_W_MaxSector. Заголов-
         ками для таблиц описателей и FCB файловой системы являются, соот-
         ветственно,  DOS_D_HDLSFT и DOS_D_FCBSFT. Листинг 4-12 показывает
         содержимое этого списка.


                        Листинг 4-12. Формат списка списков
         ----------------------------------------------------------------

         DOS                   STRUC
         DOS_D_DCB       DD    0 ; начало списка для последовательности
                                 ; блоков управления устройством (DCВ)
         DOS_D_HDLSFT    DD    0 ; начало списка описателя SFT
         DOS_D_Clock     DD    0 ; оглавление устройства для CurClk
         DOS_D_Console   DD    0 ; оглавление устройства для консоли
         DOS_W_MaxSector DW    0 ; размер наибольшего сектора
         DOS_D_Cache     DD    0 ; начало списка для блоков управления
                                 ; кеш (CCB)
         DOS_D_CDS       DD    0 ; адрес структуры текущего каталога
         DOS_D_FCBSFT    DD    0 ; начало списка FCB SFT
         DOS_W_Unknown   DW    0 ; неизвестно
         DOS_B_DriveCountDB    0 ; максимальное количество драйверов
                                 ; (значение устанавливается по lastdrive=)
         DOS_B_LastDrive DB    0 ; текущее количество драйверов
         DOS             ENDS
         ----------------------------------------------------------------

                             Системная таблица файлов

              Из всех структур данных,  за которыми  обращаются  в  список

                                      - 4-19 -
         списков,  для  TSR наиболее важны входы в таблицу системы файлов.
         Информация, которую содержат эти входы, влияет на способ обработ-
         ки резидентными программами запросов на ввод/вывод. Эти структуры
         данных, которые размещены во внешней области данных DOS, содержат
         один или несколько блоков.  Каждый блок содержит заголовок, кото-
         рый указывает на следующий блок,  и несколько входов таблицы фай-
         ловой системы. Каждый вход SFT является структурой данных.
              Длина заголовка 6 байтов.  Первое поле - это двойное  слово,
         которое содержит адрес следующего блока в таблице файловой систе-
         мы или единицу для обозначения конца списка. Второе поле - слово,
         которое указывает количество входов системы. Листинг 4-13 иллюст-
         рирует структуру SFT.


               Листинг 4-13. Оглавление блока таблицы файлов системы
         ----------------------------------------------------------------

          SFTTBL          STRUC
          SFTTBL_D_Next   DD    0
          SFTTBL_W Count  DW    0
          SFTTBL          ENDS

          SFTTBL_K_Size   EQU   SIZE SFTTBL ; определено для дальнейшего
                                            ; использования
         ----------------------------------------------------------------

              Многие поля каждого входа SFT важны только для блоковых уст-
         ройств,  но значение счетчика обращений и поле хозяина PSP непос-
         редственно касаются TSR.  Когда DOS открывает файл,  он  помещает
         вход в  таблицу  файловой системы и записывает текущий PSP в поле
         хозяина PSP со смещением 22h. Так как только владелец файла может
         закрыть его, то перед запросом к DOS закрыть файл, Вы должны быть
         уверены,  что именно Ваш PSP установлен в качестве текущей  прог-
         раммы.  Так же перед окончанием работы Вы должны восстановить PSP
         первоначальной программы переднего плана.
              Счетчик обращений  является  первым  полем  входа и содержит
         слово,  в котором записано,  сколько раз файл или устройство были
         открыты.  Перед  размещением нового входа,  DOS проверяет все су-
         ществующие входы для проверки, не открыт ли уже файл или устройс-
         тво,  к которым  сделан запрос. Если вход SFT уже существует, DOS
         увеличивает счетчик обращений перед размещением нового входа. DOS
         уменьшает  счетчик обращений,  когда файл/устройство закрываются,
         но не освобождает вход до обнуления счетчика обращений.
              Когда DOS обрабатывает запрос на открытие или создание  (че-
         рез FCB или описатель),  он записывает текущий PSP в поле хозяина
         SFT  и записывает биты состояния (означающие запрос на открытие в
         исключительном режиме или в режиме чтения) в поле режима SFT, ес-
         ли  файл не был открыт раньше.  Биты состояния определяют,  какой
         тип доступа будет разрешен.

                         Листинг 4-14. Структура входа SFT
         ----------------------------------------------------------------

         SFT   STRUC
         SFT_W_RefCnt    DW   0 ; [00] счетчик обращений
         SFT_W_Mode      DW   0 ; [02] режим открытия
         SFT_B_DirAttrib DB   0 ; [04]

                                      - 4-20 -
         SFT_W_Flags     DW   0 ; [05]
         SFT_D_DCB       DD   0 ; [07] (FILE) блок управления устройством
         SFT_W_Cluster1  DW   0 ; [0b] (FILE) начальный кластер
         SFT_W_HHMMS     DW   0 ; [0d] (FILE) часы, минуты, секунды
         SFT_W_YYMMDD    DW   0 ; [0f] (FILE) год, месяц, день
         SFT_D_FilSiz    DD   0 ; [11] размер файла/размещение EOF
         SFT_D_FilPos    DD   0 ; [15] текущая позиция файла
         SFT_W_RelClstr  DW   0 ; [19] (FILE) начало кластеров
         SFT_W_CurClstr  DW   0 ; [1b] (FILE) текущий кластер
         SFT_W_LBN       DW   0 ; [1d] (FILE) номер блока
         SFT_W_DirIndex  DB   0 ; [1f] (FILE) индекс каталога
         SFT_T_FileName  DB   0bh  DUP  (0)  ; [20] (FILE) имя файла
         SFT_T_Unknown   DB   04h  DUP  (0)  ; [2b] неизвестно
         SFT_W_OwnerMach DW   0 ; [2f] номер машины владельца файла
         SFT_W_OwnerPSP  DW   0 ; [31] PSP задачи, которая начинается
         SFT_W_Status    DW   0 ; [33]
         SFT   ENDS

         SFT_K_Size      EQU   SIZE SFT
         ;
         ;MOde field
         ;
         SFT_M_FCB       EQU  8000h ; вход для FCB
         SFT_M_DenyNone  EQU  0040h ; разделяемые биты (4-6)
         SFT_M_DenyRead  EQU  0030h ; "
         SFT_M_DenyWrite EQU  0020h ; "
         SFT_M_Exclusive EQU  0010h ; "
         SFT_M_NetFCB    EQU  0070h ; это сетевой FCB
         SFT_M_Write     EQU  0001h ; биты доступа к файлу
         SFT_M_Read      EQU  0000h ; "
         ;
         ;Flags Field
         ;
         SFT_M_Shared    EQU  8000h ; сетевой доступ
         SFT_M_DateSet   EQU  4000h ; набор данных (только для FILE)
         SFT_M_IOCTL     EQU  4000h ; поддержка IOCTL (только для DEVICE)
         SFT_M_IsDevice  EQU  0080h ; вход для устройства
         SFT_M_EOF       EQU  0040h ; (DEVICE) конец ввода файла
         SFT_M_Binary    EQU  0020h ; (DEVICE) прозрачный режим
         SFT_M_Special   EQU  0010h ; (DEVICE) поддерживает вывод int 29h
         SFT_M_IsClock   EQU  0008h ; (DEVICE) устройство текущего времени
         SFT_M_IsNul     EQU  0004h ; (DEVICE) текущее фиктивное устройство
         SFT_M_IsStdOut  EQU  0002h ; (DEVICE) текущее устройство вывода
         SFT_M_IsStdIn   EQU  0001h ; (DEVICE) текущее устройство входа
         SFT_M_Written   EQU  0040h ; (FILE) пользовательский файл
         SFT_M_DriveMask EQU  003fh ; (FILE) маска для битов драйвера (0-5)
         ----------------------------------------------------------------

                        Сегмент программного префикса (PSP)

              Когда DOS загружает программу,  он создает сегмент  префикса
         программы.  В  предыдущей главе описаны многие из полей PSP.  DOS
         всегда помещает PSP на шестнадцатибайтовую границу параграфа, так
         что он может быть описан, как значение длиной в слово (сегмент, а
         смещение ноль).  Команда DOS 62h возвращает адрес текущего PSP  в
         регистре  BX (недокументированная функция AH=51h также возвращает
         PSP в BX).

                                      - 4-21 -
              Листинг 4-15   показывает   структуру    PSP.    Поля    PSP
         PSP_D_JFTAddr и PSP_W_JFTSize содержат  адрес  и  размер  рабочей
         таблицы  файлов  (JFT).  PSP  содержит также копию (по умолчанию)
         JFT,  начинающуюся с JFT_T_JFT.  DOS использует некоторые  другие
         поля  PSP  для обработки критических ошибок и запроса завершения;
         подробнее об этих полях позднее.


                            Листинг 4-15. Структура PSP
         ----------------------------------------------------------------

         PSP   STRUC
         PSP_W_int20     DW    0cd20h ; [00] команда int 20
         PSP_W_MemSiz    DW    0      ; [02] начало памяти (para)
         PSP_B_Unused0   DB    0      ; [04] неизвестно
         PSP_T_Call      DB    09aH,0f0h ; [05] дальний вызов DOS
                         DB    0feH,01dh,0f0h ; диспетчер (CPM relic)
         PSP_D_Term      DD    0      ; [0a] конечный адрес
         PSP_D_Break     DD    0      ; [0e] адрес прерывания
         PSP_D_CritErr   DD    0      ; [12] критическая ошибка
         PSP_W_Parent    DW    0      ; [16] родительский PSP
         PSP_T_JFT       DB    14h   DUP   (0ffn) ; [18] таблица JFT
         PSP_W_Envron    DW    0      ; [2c] окружение
         PSP_D_SSSP      DD    0      ; [2e] SS:SP пользователя на
                                      ; время int 21
         PSP_W_JFTSize   DW    14h    ; [32] размер JFT
         PSP_D_JFTAddr   DD    0      ; [34] адрес JFT
         PSP_D_NextPSP   DW    0ffffH,0ffffh ; [38] не применяется
         PSP_T_Unused2   DB    14h    DUP  (0) ; [3c] не применяется
         PSP_W_Int21     DW    0cd21h ; [50]
         PSP_B_Retf      DB    0      ; [52]
         PSP_T_Unused3   DB    9     DUP  (0) ; [53]
         PSP_T_Parm1     DB    10h   DUP  (0) ; [5c] форматировано param 1
         PSP_T_Parm2     DB    14h   DUP  (0) ; [6c] форматировано param 2
         PSP_T_DTA       DB    80h   DUP  (0) ; [80] по умолчанию DTA
         PSP   ENDS
         ----------------------------------------------------------------

                           Рабочая таблица файлов (JFT)

              В большинстве случаев PSP будет содержать саму рабочую  таб-
         лицу  файлов.  По умолчанию JFT позволяет открыть одновременно 20
         файлов,  но имеется возможность создания альтернативной  JFT  для
         увеличения  максимального  количества открытых файлов.  В DOS 3.3
         для этого  имеется  специальная  функция (int 21h AH=67h).  В DOS
         версии ниже 3.3 можно изменять адрес JFT в PSP вручную.  DOS  для
         ввода/вывода будет использовать заново определенную JFT, но будет
         иметь  трудности клонирования этой JFT при обработке запроса  за-
         грузки (int 21h AH=4bh).
              Рабочая таблица  файлов  заносит описатели в качестве входов
         таблицы файловой системы. Каждый вход JFT занимает один байт. Ес-
         ли вход не использован,  он содержит 0FFh;  в противном случае он
         содержит системный номер файла(SFN), который используется как ин-
         декс в таблице файловой системы. DOS использует описатель файла в
         JFT как индекс.
               Листинги 4-16 и 4-17 иллюстрируют связи между PSP, JFT, SFN
         и SFT. Первая подпрограмма принимает описатель  в BX и возвращает

                                      - 4-22 -
         соответствующий системный  номер  файла (SFN) в AX.  Подпрограмма
         использует функцию BIOS AH=62h для размещения текущего PSP, когда
         получает  адрес JFT из PSP,  и в конечном счете использует описа-
         тель, как индекс в JFT. Макроопределения pushr и popr сохраняют и
         перезапоминают регистры,  описанные как аргументы.  Если встрети-
         лась ошибка,  то эта подпрограмма возвращает флаг переноса  уста-
         новленным (CY=1).
              Вторая подпрограмма  принимает  SFN  в AX и возвращает адрес
         соответствующего входа SFT в ES:DI.  Она получает  адрес  "списка
         списков" с функцией AH=52h и затем получает описатель  списка за-
         головков SFT в ES:DI.  Каждый блок имеет "следующее" поле и часть
         оглавления, которая показывает,  сколько входов в этом блоке. Эта
         подпрограмма просматривает цепочку блоков SFT до тех пор, пока не
         найдет блок, содержащий вход SFT. Если описатель  неверен или ес-
         ли SFT испорчена,  подпрограмма возвращает флаг переноса установ-
         ленным.

               Листинг 4-16.  Использование описателя для получения
                              номера системного файла
         ----------------------------------------------------------------

         GetSFN          PROC  NEAR
                         pushr <ds,es,di,bx>; макрокоманда сохранения
                                            ; регистров
                         mov   ah,62h...    ; получить текущий PSP
                         int   21h
                         mov   ds,bx        ; ds <== текущий PSP
                         pop   bx           ; описатель
                         cmp   bx,0ffh      ; проверка описателя
                         Jz    BadHandle    ; описатель не может быть
                                            ; отрицательным
                         cmp   bx,ds:PSP_W_JFTSize
                                            ; описатель слишком велик?
                         jge   BadHandle    ; если ge - да
                         les   di,ds:PSP_D_JFTAddr
                                            ; es:di <== JFT
                         mov   al,es:[di][bx] ; al <== SFN (описатель)
                         cbw                ; ax <== SFN (описатель)
                         clc                ; успешная индикация
         Done: popr            <di,es,ds>   ; восстановление регистров
                         ret                ; возврат
         BadHandle       stc                ; ошибка индикации
                         jmp   SHORT Done   ; общий выход
         GetSFN          ENDP
         ----------------------------------------------------------------


                   Листинг 4-17. Поиск системной таблицы файлов
         ----------------------------------------------------------------

         LocateSFT       PROC   NEAR
                         push   ax         ; сохранение SFN
                         mov    ah,52h     ; запрос адреса
                         int    21h        ; списка списков
         ;
         ;        es:di <== 1-ый блок описателя оглавления списка SFT
         ;

                                      - 4-23 -
                         les    di,es:[bx].DOS_D_HDLSFT
                         pop    ax         ; восстановление SFN
                         xor    bx,bx      ; bx <== 0
         _l0             cmp    di,0ffffh  ; конец последовательности
                         jz     _l2        ; если z - да
         ;
         ;               bx <==  первый SFN в следующем блоке
         ;
                         add    bx,es:[di].SFTBLK_W_Count
                         cmp    ax,bx       ; SFN в этом блоке?
                         jl     _l1         ; если l - да
         ;
         ;               es;ds <== следующий блок SFT
         ;
                         les    di,es:[di].SFTBLK_D_Next
                         jmp    SHORT _l0    ; продолжение поиска
         ;
         ;               bx <== первый SFN этого блока
         ;
         _l1             sub    bx,es:[di].SFTBLK_W_Count
                         sub    ax,bx        ; ax <== смещение блока
                         mov    bl,SFT_K_SIZE; bl <== размер входа
                         mul    bl           ; перевод смещения в байты
                         add    di,ax        ; di <== смещение в блоке
                                             ; (почти)
                         add    di,SFTBLK_K_Size ; добавить сверху
                         clc                 ; успешная индикация
                         ret                 ; возврат
         _l2             stc                 ; ошибка индикации
                         ret                 ; возврат
         LocateSFT       ENDP
         ----------------------------------------------------------------

                              Диспетчер BIOS, Int 21h

              Когда загружается  DOS,  IBMDOS  инициализирует  для int 21h
         вход IVT, чтобы указать на код внутри загрузочного модуля IBMDOS.
         ISR  обрабатывает все запросы int 21h.  Так как эта программа пе-
         реключает стеки и использует статические переменные,  она нереен-
         терабельна. Если TSR запрашивает обслуживание BIOS в неподходящее
         время, она испортит сохраненную DOS информацию о программе перед-
         него плана.  Результаты этого разрушения обычно катастрофические.
         Если Вам повезет,  Ваша система гробанется, не испортив Ваш диск.
              Обработка Int 21h начинается с прерываний, запрещенных в ре-
         зультате команды  INT.  Диспетчер  содержит  таблицу  действующих
         подпрограмм, которые завершают обработку различных запросов BIOS.
         Она содержит точки входа для каждой действующей функции  int 21h.
         Каждой строке этой таблицы непосредственно предшествует байт, со-
         держащий номер входа таблицы. В конечном счете DOS использует код
         функции в AH,  как индекс в этой таблице и сначала проверяет зна-
         чение,  переданное в AH.  Если запрос не выполнен,  то  диспетчер
         возвращает ошибку.
              Диспетчер Int 21h немедленно обслуживает запросы функций:
         AH=51h (недокументированная - получить текущий PSP),
         AH=62h (документированная - получить текущий PSP),
         AH=50h  (недокументированная  -  получить  текущий PSP),
         AH=33h (получить/установить прерывание).

                                      - 4-24 -
         Так как диспетчер не переключает стеки и не сохраняет контекстную
         информацию в статических переменных,  эти запросы всегда безопас-
         ны.
              Все по-другому,  если запрос не является одним из этих четы-
         рех  немедленно обслуживаемых запросов (почти все остальные функ-
         ции Int 21h).  DOS cохраняет все регистры в текущем стеке, сохра-
         няет   текущее   содержание  DS:BX  в  статической  переменной  и
         увеличивает флаг критического  интервала  (известный  также,  как
         InDOS).  Для  продолжения обработки запроса BIOS диспетчеру нужны
         регистры DS и BX;  DOS будет перезагружать эти регистры перед пе-
         редачей управления подпрограмме, которые будут завершать обработ-
         ку запроса.
              В это  время  регистры  SS:SP  все  еще содержат адрес стека
         программы переднего плана.  DOS записывает в статических перемен-
         ных значения SS:SP для текущего и предшествующего входа (то есть
         соответствующие последние значения SS:SP  диспетчера).  Диспетчер
         также сохраняет текущие значения SS:SP в текущем PSP со смещением
         16h. DOS использует стековые величины в PSP для обработки  крити-
         ческой  ошибки;  он использует эти величины как общие переменные,
         когда он возвращает управление и должен восстановить первоначаль-
         ный  стек.
              Подпрограмма диспетчера использует  три  собственных  стека:
         внешний  стек,  пользовательский  стек и стек ввода/вывода диска.
         Сохранив программный стек,  диспетчер делает безусловное переклю-
         чение  на внешний стек и разрешает прерывания.  Если запрос нахо-
         дится в диапазоне от 01h до 0Сh, и диспетчер не обрабатывает кри-
         тическую ошибку, то он переходит на стек ввода/вывода. Все другие
         запросы, кроме Get Extended Error (AH=59h), диспетчер обслуживает
         стеком ввода/ вывода диска.
              Если запрос должен быть обслужен стеком ввода/вывода диска и
         breaks разрешены, то перед выполнением запроса диспетчер выполня-
         ет проверку на break. Функции от 01h до 0ch явно проверяют breaks
         при необходимости.  (Некоторые из них явно игнорируют breaks; все
         другие проверяют breaks. Информация для выполнения описания таких
         запросов приведена в "IBM Technical Reference Manual").
              Диспетчер int  21h  использует  код  функции AH как индекс в
         таблице действующих подпрограмм,  перезапоминает DS:BX и передает
         управление работающей подпрограмме. После ее выполнения диспетчер
         запрещает breaks,  уменьшает флаг критического интервала, переза-
         поминает регистры SS:SP,  перезапоминает значения регистров перед
         int 21h и выходит из прерывания через IRET.  Действующая подпрог-
         рамма,  которая нуждается для возврата значений в модификации ин-
         дексных регистров,сохраняет значения регистров в стеке программы.

                             Подпрограммы в/в символов

               Функции BIOS  в диапазоне 01 - 0ch известны как функции в/в
         символов,  потому что это следует из  способа  их  использования.
         Операции  в/в символов занимают относительно много времени.  BIOS
         может ожидать ввод, чтобы удовлетворить запрос чтения. Вывод сим-
         волов  также  занимает  относительно  много времени.  Большинство
         функций ввода символов вызывают подпрограмму  опроса  клавиатуры.
         Подпрограмма  опроса  клавиатуры неоднократно проверяет консоль и
         устройство стандартного ввода на наличие прерываний и затем  про-
         веряет устройство ввода на наличие символов.Если символов в нали-
         чии нет,то подпрограмма опроса клавиатуры всегда вызывает диспет-

                                      - 4-25 -
         чер фонового раздела.  Функция вывода на дисплей (AH=2)  вызывает
         фоновый диспетчер каждый раз, когда она записывает 4 символа. Фо-
         новый диспетчер выполняет прерывание int 28h.
             Подпрограммы обработки прерывания (ISR) для int 28h при фоно-
         вой обработке играют важную роль.  В целях  повышения  надежности
         обработки прерываний ISR int 28h выполняет запросы BIOS, обслужи-
         ваемые в стеке дискового в/в. Все прерывания функций int 21h, но-
         мер которых больше номера 0Ch (за исключением функций,  обрабаты-
         ваемых без переключения стека), DOS обслуживает в стеке дискового
         в/в.

                             Глобальные переменные DOS

             Для сохранения  информации о состоянии функционирования и для
         поддержки контекста запросов BIOS операционная  система  DOS  ис-
         пользует  многие глобальные переменные.  DOS обеспечивает рабочую
         область буферизованного ввода и для поддержки буферизованного ре-
         дактирования  входной  строки сохраняет дорожку текущего столбца.
         Одни переменные управляют  протоколированием  экрана,  алгоритмом
         распределения памяти и текущим переключением символов. Глобальные
         переменные включают флажки критической ошибки и критической  сек-
         ции,  текущую PSP и текущую дисковую область передачи (DTA - disk
         transfer area).  Другие глобальные переменные описывают  операции
         обработки в/в; они записывают адреса входов SFN, JFT и много дру-
         гой важной и полезной информации о запросе.
             DTA является "коварной" структурой данных, потому что DOS ис-
         пользует ее в непредсказуемых случаях. Для грамматического разбо-
         ра имен файлов и поиска каталогов DOS поддерживает свою собствен-
         ную  DTA,  а  результаты  этих  операций  она  копирует   в   DTA
         пользователя.  DOS замещает адрес DTA адресом буфера для чтения и
         записи обрабатываемого файла.  Такие операции как find first/find
         next  (найти  первый/найти  следующий) осуществляют запись непос-
         редственно в текущий DTA.
             Запросы, выполняемые с помощью TSR, как "побочный" эффект мо-
         гут изменить глобальные переменные DOS.  Операционная система DOS
         не ожидает,  что другая программа будет просматривать ее глобаль-
         ные переменные,  и,  вероятно,  придет в замешательство, если эти
         глобальные переменные будут изменены.

                                  Обработка break

             DOS проверяет наличие break в двух случаях.  Если запрос дол-
         жен быть обработан в дисковом стеке и если проверка break  разре-
         шена, диспетчер прерывания int 21h вызывает подпрограмму проверки
         break. Подпрограмма опроса клавиатуры (вызываемая с помощью функ-
         ций в/в символов) во время ожидания ввода и во время ожидания вы-
         вода на stdOut проверяет break.
             Подпрограмма проверки break проверяет текущее устройство кон-
         соли.DOS идентифицирует устройство консоли путем проверки атрибу-
         тов драйверов устройства, когда она загружает их (см.  главу  6).
         В своем заголовке устройство текущей консоли будет иметь установ-
         ленные биты IsStdIn и IsStdOut. Адрес устройства текущей  консоли
         DOS  записывает  в список списков. DOS будет проверять устройство
         консоли на break,  даже  если  некоторые программы  переназначают
         stdin.  Существует  неявное  предположение о том,  что устройство
         консоли было объявлено сервисной подпрограммой прерывания int 1Bh
         и может  получать уведомление о break асинхронно. Побочный эффект
         такого объявления состоит в том, что если  stdin  переназначается

                                      - 4-26 -
         на файл и программа выполняет его чтение с помощью функции преры-
         вания int 21h,  номер которых больше 0Ch, то клавиша Control-C не
         имеет своего обычного предназначения.
             Подпрограмма опроса  клавиатуры сначала вызывает подпрограмму
         проверки break и затем проверяет  стандартный  ввод.  Когда  идут
         операции в/в символов,  DOS обнаруживает break либо от устройства
         консоли,  либо от стандартного ввода;  но когда DOS  работает  со
         стеком дискового в/в, то проверяется только устройство консоли.
             Подпрограмма DOS, обрабатывающая break, устанавливает SS:SP в
         значение, записываемое с помощью диспетчера int 21h, восстанавли-
         вает все регистры в состояние, предшествующее прерыванию int 21h,
         сбрасывает флажки критической секции и критической ошибки,  и вы-
         полняет инструкцию int 23h. ISR int 23h может возвратить управле-
         ние  в  подпрограмму обработки break DOS либо по инструкции IRET,
         либо по инструкции RET. Выполнение инструкции IRET удаляет 6 бай-
         тов  из  стека,  в то время как выполнение возврата far (далекий)
         удаляет только 4 байта.  Путем выполнения сравнения значений в SP
         до и после выполнения инструкции int 23h,  подпрограмма break мо-
         жет сообщить, какая инструкция (RET или IRET) возвратила управле-
         ние.
             Если ISR int 23h сохраняет используемые ею регистры,  то  она
         может &продолжать выполнение с помощью инструкции IRET.  Если ISR
         возвращает управление с помощью возврата far (далекий), то, будет
         или нет продолжено выполнение,  определяется состоянием флага пе-
         реноса.  Если флаг переноса очищен, то выполнение будет продолже-
         но,  иначе  -  программа  будет аварийно завершена.  Подпрограмма
         break DOS вынуждает выполнить аварийное завершение путем загрузки
         в регистр AX значения 4C00h. Во всех случаях управление возвраща-
         ется в начало диспетчера int 21h. Затем диспетчер повторно выпол-
         няет  запрос  int  21h,  или выполняет запрос завершения в случае
         аварийного завершения.
             По умолчанию  ISR  int  23h  содержит  инструкцию IRET.  Файл
         COMMAND.COM устанавливает свою собственную ISR int  23h,  которая
         аварийно завершает текущую программу.  Другие программы могут ус-
         танавливать свои собственные подпрограммы обслуживания int 23h.

                           Обработка критической ошибки

             Многие запросы прерываний  int  21h  вызывают  операции  в/в.
         Большинство  запросов  на  в/в BIOS передает драйверу устройства.
         Если драйвер устройства не может завершить запрос, он сообщает об
         этом в BIOS.  BIOS отвечает на ошибки устройства  объявлением со-
         стояния критической ошибки. В  ответ  на  ошибку  устройства  DOS
         уменьшает значение флажка критической секции и увеличивает значе-
         ние флажка критической ошибки.  Порча блоков FAT  также  вызывает
         состояние критической ошибки.
             При обнаружении критической ошибки DOS выполняет одно из сле-
         дующих четырех действий:  игнорирует ошибку;  повторяет операцию,
         вызвавшую ошибку; завершает текущую программу или выполняет отказ
         текущему  вызову.  Однако,  все  эти  четыре опции имеют место не
         всегда. Для выбора образа действий DOS использует флажок.
             Если DOS уже обрабатывает критическую ошибку, то подпрограмма
         обработки критической ошибки отвергает отказ вызова, который при-
         вел ко второй критической ошибке.  Чтобы увидеть,  находится ли в
         обработке описатель запроса на в/в, подпрограмма обработки крити-
         ческой ошибки проверяет некоторую глобальную переменную DOS. Если
         это так,  то DOS выбирает адрес входа JFT этого описателя из дру-
         гой глобальной переменной и помечает этот описатель как  неправи-

                                      - 4-27 -
         льный; это действие предотвращает другую  критическую  ошибку  от
         того же самого описателя.
             В случае блокировки прерывания подпрограмма обработки  крити-
         ческой  ошибки  увеличивает  значение  флажка критической ошибки,
         уменьшает значение  флажка  критической  секции,  восстанавливает
         значения SS:SP, сохраненные диспетчером int 21h, и выполняет инс-
         трукцию int 24h.  Когда ISR int 24h осуществляет возврат управле-
         ния,  подпрограмма  обработки  критической ошибки восстанавливает
         пару SS:SP (т.к. ISR int 24h может изменить ее), увеличивает зна-
         чение флажка критической секции и устанавливает начальное состоя-
         ние флажка критической ошибки.
             Подпрограмма обработки  критической ошибки ожидает ISR преры-
         вания int 24h,  для того чтобы возвратиться к обработке. Если ISR
         запрашивает приемлемое действие, то подпрограмма обработки крити-
         ческой ошибки выполняет его.  Если подпрограмма обработки  крити-
         ческой ошибки пометила описатель как неправильный, то перед выхо-
         дом она восстанавливает SFN из глобальной переменной DOS. Запросы
         на завершение проходят через обработчик прерываний,  который зас-
         тавляет диспетчер int 21h выполнить запрос на завершение.
             Когда инициируется  файл  COMMAND.COM,  он устанавливает свою
         собственную ISR int 24h;  она является той подпрограммой, которая
         выдает сообщение "Abort,  retry or ignore ?" (Завершить аварийно,
         повторить или игнорировать ?).  Другие программы также могут объ-
         являть свои собственные подпрограммы ISR int 24h.

                                Загрузка программы

             Все программы загружает общая служба BIOS. Подпрограмма функ-
         ции AH=4Bh int 21h устанавливает операционную среду, распределяет
         память  для загрузки программы, загружает программу с диска и со-
         здает PSP. Для загрузки программы она использует наибольший  блок
         памяти. Файлы типа .EXE указывают свою потребность в памяти в за-
         головке программы,  и подпрограмма загрузки  устанавливает  соот-
         ветствующий размер блока памяти. Размер файла типа .COM определя-
         ется его  минимальной  потребностью  в  памяти,  но  подпрограмма
         загрузки  не настраивает размер блока для файлов типа .COM.  Файл
         типа .COM начнет выполняться во всем блоке памяти, распределенным
         для него.
             Обычно DOS начинает загрузку программы,  потому что пользова-
         тель указал ее имя после  приглашения,  введенного оболочкой. За-
         гружаемая при этом программа называется порождаемой, а программа,
         выдающая запрос на загрузку,  называется порождающей. Порождающая
         программа создает блок параметров,  содержащий адрес таблицы сре-
         ды, адрес командной строки и адреса двух блоков управления файла-
         ми (FCB - file control block). Порождающая программа передает ад-
         рес  этого  блока  параметров  и  адрес спецификации файла в коде
         ASCIIZ в подпрограмму выполнения загрузки,  используя  запрос  на
         выполнение функции AX=4B00h прерывания int 21h. Порождающая прог-
         рамма может указать явно размещение операционной среды  или может
         выдать  запрос на копирование своей операционной среды путем ука-
         зания нуля в качестве начального сегмента среды. Если порождающая
         программа не имеет среды,  но при этом выдает запрос, чтобы копи-
         ровалась ее среда, то порожденная программа не будет иметь опера-
         ционной среды.
             В предыдущем  разделе  обсуждались подробности загрузки прог-
         рамм. Этот процесс важен, но не настолько интересен, как реализа-
         ция  TSR.  После  загрузки образа программы с диска,  DOS создает

                                      - 4-28 -
         сегмент префикса программы.  Содержимое этого PSP является важным
         для TSR. Та же самая программа, которая обслуживает запрос созда-
         ния PSP (int 21h AH=26h),  создает PSP для подпрограммы загрузки.
             Перед вызовом подпрограммы создания PSP, подпрограмма загруз-
         ки  устанавливает флаг,  который заставляет подпрограмму создания
         PSP инициализировать JFT порожденного процесса. Подпрограмма соз-
         дания PSP  рассматривает каждый вход в JFT порождающего процесса,
         находит соответствующий ему вход SFT,  и клонирует ссылку до  тех
         пор, пока в SFT не будет установлен бит "не наследовать", или ес-
         ли вход не будет соответствовать сетевому FCB.  Клонирование уве-
         личивает  счетчик  ссылки  SFT  и копирует SFN в JFT порожденного
         процесса. Обычно говорят, что порожденный процесс "наследует" эти
         файлы. Файл COMMAND.COM использует наследственность для обеспече-
         ния переназначения устройств стандартного ввода stdin и стандарт-
         ного  вывода  stdout.  Так как прикладная программа наследует эти
         файлы,  то она не должна выполнять их явное открытие.  Входы  JFT
         для этих описателей уже содержат допустимые номера системных фай-
         лов,  скопированных из порождающего процесса. Второй побочный эф-
         фект установки флага состоит в том, что PSP порожденного процесса
         становится текущим PSP. Подпрограмма создания PSP:
          - заполняет несколько других полей PSP;
          - копирует в PSP порожденного процесса содержимое текущих входов
            IVT для прерываний по завершению (int 22h),  break (int 23h) и
            критической ошибки (int 24h);
          - возвращает управление в подпрограмму загрузки.
                Подпрограмма загрузки:
          - заполняет адреса  среды, инициализирует два входа FCB PSP;
          - копирует адрес возврата управления порождающего  процесса  для
            вектора завершения (int 22h);
          - устанавливает адрес передачи с диска в PSP  порожденного  про-
            цесса 80h;
          - инициализирует регистры ES,  DS, SS и SP и передает управление
            в порожденный процесс.

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

             Имеется несколько  различных  способов  завершения выполнения
         обычной программы. Наиболее общими являются использование функции
         AH=4Ch и функции AH=00h прерывания int 21h.  Все запросы заверше-
         ния обрабатывает общая подпрограмма DOS. При завершении программы
         эта  подпрограмма  копирует адреса критической ошибки (int 24h) и
         прерывания ISR (int 23h),  сохраненные в PSP для  IVT,  закрывает
         все  файлы и освобождает всю память,  относящуюся к текущему про-
         цессу.  Управление возвращается по адресу завершения  (int  22h).
         Если завершающий процесс не модифицировал вход IVT для адреса за-
         вершения,  то программа,  загрузившая программу завершения, снова
         получит  управление  для  выполнения инструкции,  непосредственно
         следующей за запросом загрузки.  Затем,  как  обычно,  управление
         возвращается в файл COMMAND.COM. Критические ошибки вызывают ава-
         рийные завершения. Одна и та же  программа  обрабатывает  запросы
         аварийного и обычного завершения, различие заключается лишь в вы-
         работке разного кода завершения, сохраняемого во внутренней пере-
         менной DOS.
             Освобождение памяти является простым процессом. DOS распреде-
         ляет память на блоки.  Каждому блоку памяти непосредственно пред-
         шествует 16-байтовый блок управления памятью (MCB).  Область  MCB
         содержит  размер следующего блока и записи PSP владельца.  Слово,
         стоящее во главе списка,  содержит сегмент первого блока управле-

                                      - 4-29 -
         ния памятью. Подпрограмма завершения DOS просматривает список MCB
         для нахождения блоков,  которыми владеет текущий процесс.  Всякий
         раз,  когда подпрограмма завершения находит очередной блок, кото-
         рым владеет процесс, она устанавливает поле владельца MCB в нуль,
         указывая,  тем самым, что блок  свободен. Таким образом, при про-
         смотре MCB освобождаются все блоки памяти, которыми владел завер-
         шаемый процесс, включая операционную среду. Для освобождения сво-
         ей среды программе не нужно предпринимать специальных действий.
             Подпрограмма завершения DOS получает адрес JFT из PSP текуще-
         го (завершающего) процесса и просматривает JFT для поиска  откры-
         тых файлов. При этом подпрограмма завершения закрывает каждый от-
         крытый файл.  Для каждого открытого файла  подпрограмма  закрытия
         уменьшает счетчик ссылок SFT.  Если счетчик ссылок станет нулевым
         и файлом владеет текущая программа,  то подпрограмма закрытия ос-
         вобождает  вход SFT.  Входы,  соответствующие наследуемым файлам,
         будут иметь счетчики ссылок, значения которых больше 1; входы SFT
         для этих ссылок останутся. (Так как завершающая программа все еще
         имеет текущий PSP, то любая попытка закрытия этих входов приведет
         к  отказу;  поле  "владелец  PSP"  этих  входов SFT содержит файл
         COMMAND.COM, выступающий в качестве их владельца).
             В DOS  имеется две функции "завершить и оставить резидентной"
         - int 27h и int 21h AH=31h. Функция int 27h является устаревшей и
         внутри DOS она отображается в запрос int 31h.  Запросы "завершить
         и оставить резидентной" обрабатываются одной и той же подпрограм-
         мой завершения.  При выполнении запроса "завершить и оставить ре-
         зидентной" подпрограмма завершения не закрывает никакие  файлы  и
         не  освобождает никакую память,  но она модифицирует размер блока
         памяти,  содержащий PSP.  Завершающая программа указывает  размер
         нового блока памяти в качестве аргумента для запроса "завершить и
         оставить резидентной".  Любые индикаторы обработки,  которые были
         допустимы  перед  выдачей запроса "завершить и оставить резидент-
         ной", будут действительны и после повторной активации TSR.

                           Загрузка и инициализация TSR

             TSR может быть либо файлом .COM, либо файлом .EXE. DOS загру-
         жает все программы одним и тем же способом. Каждая программа име-
         ет сегмент программного префикса (PSP),  код программы и  данные.
         Различие между TSR и стандартными прикладными программами состоит
         в том,  что TSR выполняет несколько основных задач для подготовки
         самой себя к последующей повторной активации.
             При инициализации TSR является программой переднего  плана  и
         ей  полностью доступна вся система DOS.  При выполнении TSR в ка-
         честве программы переднего плана ей доступна определенная  инфор-
         мация. TSR должна записывать любую часть этой информации на одном
         из этапов ее инициализации. Во время инициализации TSR обычно вы-
         полняет следующие действия:
             - проверяет версию используемой DOS;
             - размещает важные структуры данных DOS;
             - "захватывает" один или более векторов прерываний;
             - проверяет  типы имеющихся дисплейных адаптеров и периферий-
               ных устройств;
             - выполняет  некоторую дополнительно указанную прикладную об-
               работку;
             - вычисляет  объем  памяти,  необходимый для размещения рези-
               дентной подпрограммы.
             Процесс инициализации TSR завершается,  когда программа вызы-

                                      - 4-30 -
         вает функцию "завершить и  оставить  резидентной"  (int  21h  AH=
         31h). Важно подчеркнуть тот факт, что раз TSR завершается, то она
         больше не является программой переднего плана.  Фоновые программы
         являются  как  бы неожиданными посетителями,  следовательно,  они
         должны быть очень аккуратными при выполнении своих  функций.  На-
         чальным  заданием программы инициализации должна быть запись сос-
         тояния системы таким образом, чтобы TSR могла бы повторно активи-
         роваться без разрушения операционной системы.
             Короче говоря, подпрограмма инициализации получает стартовав-
         шую  программу,  гарантирует,  что  TSR сможет быть выполнена при
         последующем вызове,  вычисляет требуемый размер памяти для  рези-
         дентной подпрограммы,  и, наконец, выдает запрос "завершить и ос-
         тавить резидентной" (int 21h AH=31h) для  возврата  управления  в
         DOS.

                         Проверка версии используемой DOS

             Перед выполнением каких-либо действий многие подпрограммы TSR
         рассчитывают на конкретную версию, недокументированные возможнос-
         ти  DOS  и  определенный порядок проверки версии текущей системы.
         Если версия некорректна,  то TSR должна осуществить выход с выда-
         чей соответствующего сообщения об ошибке.
             DOS записывает  номер  версии своей системы в глобальной пере-
         менной и делает доступным это значение через запрос к BIOS с функ-
         цией 30h. При выполнении этого запроса диспетчер int 21h не выпол-
         няет переключение стеков или изменение любых  глобальных  перемен-
         ных.  Хотя этот запрос всегда безопасен, правила хорошего тона при
         программировании требуют, чтобы этот запрос выдавался бы среди ко-
         дов, выполняющих в программе инициализацию. Пример определения но-
         мера версии  используемой  операционной  системы  показан  ниже  в
         листинге 4-18.

                         Листинг 4-18. Проверка версии DOS
         ----------------------------------------------------------------

          VersionID   EQU   0a03h         ; DOS 3.10 (заметим, что млад-
                                          ; шая часть номера в MCB)
                      mov   ah,30h        ; ah <== функция для проверки
                                          ; версии DOS
                      int   21h           ; выдача запроса
                      cmp ax,VersionID    ; версия  возвращается  в  ax
                      jnz WrongVersion    ; версия ошибочна
         ----------------------------------------------------------------

                       Размещение резидентных копий TSR

             Управление некоторыми действиями  DOS  и  работой  аппаратных
         средств предписывает использование входа IVT. TSR также использу-
         ет прерывания и входы IVT TSR для  размещения  резидентных  копий
         своих программ.  При этом может возникнуть необходимость размеще-
         ния в памяти нескольких копий резидентных программ TSR или  необ-
         ходимость  размещения данных,  записываемых с помощью резидентной
         программы.  Если при выполнении TSR выбирает некоторый вход  IVT,
         то  последовательность  выполняемых  программ активации размещает
         резидентную программу путем выполнения инструкции INT или провер-
         ки программного кода, указываемого входом IVT.
             Какой вход IVT следует выбрать?  Это,  прежде всего, определя-

                                      - 4-31 -
         ется тем,  что  выбор прерывания для размещения резидентной прог-
         раммы зависит от автора TSR.  Абсолютно простой механизм  отсутс-
         твует.
             DOS и аппаратные средства персонального компьютера используют
         только некоторые из имеющихся в распоряжении входов IVT. Теорети-
         чески можно выбрать любой неиспользуемый вход.  Если TSR действи-
         тельно выполняет инструкцию INT,  то вход IVT должен указывать на
         допустимую программу обработки прерывания (ISR). Однако, гарантия
         того,  что вход IVT ее содержит, если TSR не инициализировала ее,
         отсутствует.  Один  из путей выхода из этой  дилеммы  "Catch  22"
         ("Ловушка 22") состоит в проверке входа IVT.
             DOS загружает все программы на границу сегмента.  Если вектор
         прерывания  "захватила"  предыдущая копия программы,  то значение
         смещения (младшее слово) во входе IVT должно соответствовать сме-
         щению ISR в текущей программе.  Так как надежда на то,  что прог-
         раммы обработки прерываний (ISR) для двух различных  программ TSR
         используют  один  и  тот же вход IVT и имеют одинаковое смещение,
         довольно слабая, то необходимо выполнить некоторую дополнительную
         проверку. Пример этого приведен в листинге 4-19.
             В этом примере ищется строка ASCII UniqueID;  мы могли бы вы-
         полнить в программе ISR сравнение строк. Недостатком этого спосо-
         ба является то, что он не разрешает проблему "конфликтующих" пре-
         рываний. Если две программы TSR решили использовать один и тот же
         вход IVT,  то  практически  не существует способа определения то-
         го, какую TSR загружать первой.
             Начиная с версии 3.0 DOS,  фирма  "Майкрософт"  документирует
         многократные прерывания,  что является ее первой попыткой решения
         проблемы  "конфликтующих"  прерываний.  Многократные   прерывания
         обеспечивают  гарантируемые  правильные  входы  IVT для int 2Fh и
         протокол размещения программ TSR.  Начальные входы IVT этого пре-
         рывания int 2Fh указывают на инструкцию IRET. Каждая TSR, ожидаю-
         щая использования мультиплексируемого  прерывания,  сначала  ищет
         предыдущие загруженные копии своей программы,  а затем устанавли-
         вает свою собственную программу ISR прерывания int 2Fh.

                Листинг 4-19.  Размещение  TSR  путем использования
                     произвольно выбранного вектора прерывания
         ----------------------------------------------------------------

         NewISRVector  EQU  ??  ; заполнение номера вектора
         OldISRxx   DD  0       ; здесь программа сохраняет старый вектор
         UniqueID   DB 'уникальная строка' ;  для идентификации ISR
         IDLength      EQU $-UniqueID ; длина строки
         NewISRxx PROC FAR      ; устанавливается программой инициализации
         ;
         ;            ...           ; все, что выполняет ISR
         ;
                      iret
         NewISRxx     ENDP
         LocateISR    PROC  NEAR
                      mov al,NewISRVector ; al <== номер вектора
                      mov ah,35h        ; ah <== получение функции вектора
                                        ; прерывания
                      int 21h ;  запрос к DOS для вектора прерывания
                       ret    ;   es:bx имеет адрес ISR
         LocateISR    ENDP
         CheckISR     PROC  NEAR
                      cmp   bx,OFFSET NewISRxx ; существующее смещение

                                      - 4-32 -
                                    ; хорошее
                      jnz   done    ; если не 0 -- нет
                      mov si,OFFSET UniqueID ;  si <==  смещение  UniqueID
                      mov di,si              ; di <== смещение UniqueID
                      mov cx,IDLength     ;  cx <== длина идентификатора
                      cld
                      repnz  cmpsb  ; сравнение идентификаторов
         done:        ret           ; возврат :
                                    ; zr=1 ==> результат установлен
         CheckISR     ENDP     ;  zr=0 ==> результат не установлен

         TSRResdnt    PROC   NEAR ; определяет резидентная ли TSR
                      call LocateISR ;  получение адреса ISR
                      call CheckISR  ; проверяет идентификатор ID
                      ret            ; и возвращает:
                                     ; zr=1 ==> результат установлен
                                     ; zr=0 ==> результат не установлен
         TSRResdnt    ENDP
         ----------------------------------------------------------------

             TSR сама  осуществляет поиск резидентных копий путем загрузки
         уникального идентификатора в регистр AH,  нуля в регистр AL и вы-
         полнения инструкции int 2Fh. Программа ISR 2Fh проверяет значение
         в регистре AH. Если ISR распознает идентификатор, то она устанав-
         ливает AL=00fh и возвращает управление по инструкции IRET; в про-
         тивном случае она переходит к ранее сохраненной ISR  int  2Fh.  В
         конце концов,  либо будет достигнут конец этой цепочки,  либо ISR
         распознает значение в регистре AH.
             Снова возможны  конфликты.  Для их разрешения TSR должна выб-
         рать дополнительные проверки.  Для облегчения этой проверки можно
         расширить протокол прерывания int 2Fh,  но при этом следует иметь
         в виду, что стандарты на дополнительные проверки отсутствуют. Не-
         обходимо защищать программу. Один из возможных подходов иллюстри-
         рует листинг 4-20.
             Фактически, получение положительного ответа на запрос int 2Fh
         AL=0 означает,  что ответила некоторая TSR. ISR int 2Fh, показан-
         ная  в листинге,  отвечает на функцию AL=1 путем возврата ее сег-
         мента кодов в регистре ES. TSR, сделавшая начальный запрос, может
         использовать  это  значение  для  нахождения некоторой уникальной
         строки.  Если строки совпадают между собой, то можно быть уверен-
         ным, что найдена правильная ISR.
             Такое расширение протокола многократного прерывания не  явля-
         ется стандартным. Отсутствует гарантия того, что некоторая другая
         TSR будет выполняться в ответ на запрос int 2Fh  AL=1.  Обнуление
         регистра ES перед выполнением этого второго запроса позволяет, по
         крайней мере, знать, что ответившая TSR возвращает некоторое зна-
         чение  в  регистре ES.  (Вам конечно известно,  что TSR не должна
         загружаться в сегмент 0).

                 Листинг 4-20.  Размещение TSR путем использования
                           мультиплексируемых прерываний
         ----------------------------------------------------------------

         OurID EQU   81h       ;  TSR выбирает значение AH
         OldISR2f  LABEL  FAR  ;здесь  сохраняется старый вектор int 2Fh

         UniqueID    DB   'уникальная строка' ;для идентификации TSR

                                      - 4-33 -
         IDLength    EQU     $-UniqueID ; длина строки
         OldInt2f    DD      0       ; здесь программа инициализации запи-
                                     ; сывает начальный адрес ISR
         NewISR2f    PROC    FAR     ; новая ISR int 2Fh
                     cmp     ah,OurID  ; запрос для нас?
                     jz      ItsMe     ; если 0 -- для нас
                     jmp     cs:OldInt2f ; передача запроса
         ItsMe:      or      al,al      ; загрузка проверена?
                     jnz     GetAddress ; если не 0 -- нет
                     mov     al,0ffh    ; загружена
                     iret               ; возврат
         GetAddress:
                     cmp     al,1    ; проверен ли адрес?
                     jnz BadFunction ; если не 0 -- нет
                     push cs ; возврат сегмента в ES
                     pop es
                     iret
         BadFunction:  stc             ; индуцирует ошибку
                     iret
         NewISR2f    ENDP
         LocateISR   PROC    NEAR
                     mov ax,OurID SHL 8 ;  ожидание чего-либо?
                     int 2fh
                     cmp al,0ffh ;  проверка выдаваемого ответа
                     jnz NotFound ; не  0  ==> нет ответа
                     xor ax,ax ;  затирание сегмента
                     mov es,ax ; проверка выдаваемого ответа
                     mov ax,(OurID SHL  8) OR 1
                               ;  запрос сегмента
                     int 2fh
                     jc NotFound ;если cy=1,  то это не нам
                     xor ax,ax  ;  изменения  ES проводились?
                     mov bx,es ; если изменения ES не проводились
                     cmp bx,ax
                     jz  NotFound ; изменения ES не проводились
                     lea bx,NewISR2f ; ES:BX имеет адрес ISR
                     clc              ; индикация успешна
                     ret              ; возврат
         NotFound:   stc
                     ret
         LocateISR   ENDP
         TSRResdnt PROC  NEAR  ;  определяет  резидентная  ли   TSR
                     call LocateISR  ;  получает  адрес  TSR
                     jc  NotLoaded
                     call CheckISR ; проверяет идентификатор ID
                     ret              ; возврат
                                      ; zr=1 ==> установлена
                                      ; zr=0 ==> не установлена
         NotLoaded:  or      al,1     ; установка zr=0
                     ret              ; возврат
         TSRResdnt   ENDP
         ----------------------------------------------------------------
             Заметим, что TSR не может просто так перехватывать прерывание
         int 2Fh.  Если некоторая другая TSR,  загружаемая  позже  других,
         "захватит" этот вектор прерывания, то вход IVT будет указывать не
         на первую TSR, а на загруженную позже других.


                                      - 4-34 -
                Запись адреса сегмента программного префикса (PSP)

             Сегмент программного  префикса (PSP) является важной структу-
         рой данных в DOS.  Операционная система DOS использует адрес  PSP
         для  управления  программами и поддержки многих служебных функций
         ввода/вывода. DOS не знает как управлять несколькими PSP, она мо-
         жет управлять только текущим PSP.  Если Ваша подпрограмма TSR пе-
         решла к выполнению какого-либо действия,  то за  судьбу  текущего
         PSP отвечаете Вы.  Позднее мы узнаем, как сообщать DOS о том, ка-
         кой PSP использовать.  Если какой-либо подпрограмме TSR после  ее
         инициализации  в  последующем  понадобится  адрес ее PSP,  то она
         должна сохранить этот адрес на  этапе  выполнения  инициализации.
         Только на этапе инициализации можно быть полностью уверенным, что
         текущий PSP относится непосредственно к Вам.  Следующая программа
         иллюстрирует, как определить адрес Вашего PSP (Листинг 4-21).

                        Листинг 4-21. Получение адреса PSP
         ----------------------------------------------------------------

         MyPSP  DW    0         ; здесь записывается адрес PSP
                mov   ah,62h    ; обращение к DOS для получения текущего-
                                ; го PSP
                int   21h       ; получение адреса PSP, относящегося к
                                ; нам
                mov   MyPSP,bx  ; сохранение PSP
         ----------------------------------------------------------------

                     Запись адреса критической секции (INDOS)
                            и адреса критической ошибки

             После того,  как  TSR  завершает выполнение запроса "оставить
         резидентной" (функция 31h),  то для последующей  своей  активации
         она ожидает прерывание захвата. Когда TSR пробуждается, необходи-
         мо иметь способ определения, что в данный момент делает программа
         переднего плана,  или кто активен в данный момент:  DOS или BIOS.
         Так как операционная система DOS не является  повторно  вводимой,
         то  в  целях  оказания  помощи резидентной подпрограмме в решении
         вопроса о безопасности  приема  запросов  BIOS  она  поддерживает
         флажки критической секции и критической ошибки.
             Для гарантии безопасности продолжения  своего выполнения  при
         повторной активации TSR должна проверять состояние обоих флажков:
         критической ошибки и критической секции. Адрес флажка критической
         секции DOS выбирает благодаря недокументированному запросу на вы-
         зов функции AH=34h прерывания int 21h.  В версии DOS 3.10 отсутс-
         твует функция BIOS для возврата адреса флажка критической ошибки;
         этот флажок размещается непосредственно перед флажком критической
         секции. В версии DOS 3.20 адрес флажка критической ошибки возвра-
         щает функция int 21h AX=5D06h в паре ES:BX, а в версиях DOS 3.3 и
         4.0 это значение возвращается в паре DS:SI.
             Учитывая способ обработки операционной системой  DOS запросов
         int 21h,  внутри ISR нельзя гарантировать надежное получение этих
         адресов.  Самым надежным способом доступа к этим флажкам является
         сохранение этих адресов только во время секции инициализации TSR.
         Следующий фрагмент программы,  приведенный в листинге  4-22,  ил-
         люстрирует захват  адресов флажков критической секции и критичес-
         кой ошибки.


                                      - 4-35 -
                Листинг 4-22. Размещение флажков критической секции
                               и критической ошибки
         ----------------------------------------------------------------

         CsectFlg   DW     0,0        ; адрес флажка критической секции
         CErrflg    DW     0,0        ; адрес флажка критической ошибки
         GetCritFlags      PROC    NEAR
                    mov    ah,30h     ;  ah <== проверка версии DOS
                    int    21h
                    cmp    al,03h     ; версия 3.00?
                    jnz    WrongVersion ; если не 0 -- нет
                    push   ax         ;  сохранение номера версии
                    mov    ah,34h     ; получить адрес флажка крити-
                    int    21h        ; ческой секции
                    mov    CSectFlg,bx ; адрес в ES:BX
                    mov    CSectFlg+2,es ; запомнить адрес
                    dec    bx         ; предполагается, что адрес флажка
                                      ; критической ошибки предшествует
                                      ; адресу флажка критической секции
                     pop   ax         ; восстановить номер версии
                     cmp   ah,1eh     ; версия 3.30?
                     jnz   v3xx       ; если не 0 -- нет
                     mov   ax,5d06h   ; получение адреса критической
                                      ; ошибки
                     int   21h        ; (только DOS 3.3)
         v3xx:       mov   CErrflg,bx ; запоминание адреса критич.ошибки
                     mov   CErrflg + 2,es ; адрес в ES:BX
                                      ; DS:SI в версиях 3.3 и 4.0
                     clc              ; индикация успешности и
                     ret              ; возврат
         WrongVersion:                ; плохая версия
                     stc              ; индикация отказа и
                     ret              ; возврат
         GetCritFlags     ENDP
         ----------------------------------------------------------------


                            Захват векторов прерываний

             В какой-либо  точке секции своей инициализации TSR может объ-
         явить свою собственную ISR int 2Fh,  чтобы впоследствии при акти-
         вациях программы можно было бы локализовать ее резидентную часть.
         Для этого TSR может также потребоваться модификация других входов
         IVT.  Прерывания int 25h (чтение с диска по абсолютным адресам) и
         int 26h (запись на диск по абсолютным адресам) затрудняют измене-
         ние  стека.  Благодаря своей природе,  прерывание int 13h (нижний
         уровень в/в диска) не может быть прервано. Представьте себе,  что
         произойдет, если прерывание с кодом int 13h было бы прервано меж-
         ду поиском и передачей.  Если при отработке этого прерывания слу-
         чится еще одна операция в/в,  то первая передача, по всей вероят-
         ности, нанесет серьезный ущерб структуре диска.
             В связи  с  этим  DOS  не подразумевает никаких прерываний во
         время обслуживания одного из этих запросов.  За защиту операцион-
         ной системы DOS в подобные моменты ответственность несет програм-
         ма TSR.  Захват этих векторов позволяет TSR управлять активностью
         диска. Эти ISR должны писаться с использованием определенных трю-
         ков из-за способа использования флажков процессора.  Исходная ISR

                                      - 4-36 -
         int 13h возвращает результат в регистре флажков; новая ISR должна
         возвращать эти результаты быстрее, чем инструкция int 13h занесет
         их в стек.  Исходные ISR int 25h и int 26h добавляют, кроме того,
         другое искажение,  занося флажки в стек инструкцией INT. Заметим,
         что новые  программы ISR NewInt25 и NewInt26 перед вызовом исход-
         ной подпрограммы не выполняют инструкцию  push,  и  что  все  эти
         ISR используют  возврат  far  далекий.  Что необходимо делать при
         захвате этих прерываний, показано в листинге 4-23.
             При захвате прерывания будьте осторожны. Так как вход IVT мо-
         дифицирован,  процессор будет диспетчировать новую ISR, даже если
         адрес ISR больше не указывает на правильную программу. Можно ожи-
         дать возникновения прерываний и критических  ошибок.  Если  после
         захвата прерывания возникнет любое из этих условий,  то они могут
         вызвать завершение программы. Операционная система DOS будет  по-
         вторно использовать память,  занятую Вашей программой и  ее  ISR.
         Так  как  это происходит,  то входы IVT недолго указывают на пра-
         вильные программы ISR.

         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         ¦                П Р Е Д О С Т Е Р Е Ж Е Н И Е                 ¦
         ¦                                                              ¦
         ¦   Перед модификацией любого вектора  прерывания   необходимо ¦
         ¦   установить свои собственные программы ISR для break и кри- ¦
         ¦   ческой ошибки.Не пытайтесь восстановить любой из этих век- ¦
         ¦   торов.При завершении Вашей программы DOS будет фиксировать ¦
         ¦   входы IVT для этих функций. Если Вы пытаетесь восстановить ¦
         ¦   адрес критической ошибки, либо адрес прерывания и захвати- ¦
         ¦   ли другие векторы,  то  Ваша  программа  станет уязвимой к ¦
         ¦   преждевременному завершению.                               ¦
         АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

            Листинг 4-23. Типовое замещение программ ISR дискового в/в
         ----------------------------------------------------------------

         DiskIO      PROC   FAR
         OldInt13    DD     0           ; здесь программа инициализации
         OldInt25    DD     0           ; записывает адреса оригинальных
         OldInt26    DD     0           ; int 13h, int 25h и int 26h
         BusyFlag    DB     -1          ; флажок защиты от прерывания
                                        ; нереентерабельной программы
         DiskIOExit0:
                     pushf              ; сохраняет флажки дискового в/в
                     dec    cs:BusyFlag ; снимает блокировку
                     popf               ; восстанавливает флажки диск.в/в
                     ret    2           ; возврат с удалением флажков, по-
                                        ; мещенных при прерывании
         DiskIOExit1:
                     pushf              ; сохраняет флажки дискового в/в
                     dec    cs:BusyFlag ; снимает блокировку
                     popf               ; восстанавливает флажки диск.в/в
                     ret
         NewInt13:
                     inc    cs:BusyFlag ; снимает блокировку
                     pushf              ; имитирует прерывание
                     call   cs:OldInt13 ; диспетчирование реальной прог-
                                        ; раммой
                     jmp    SHORT DiskIOExit0

                                      - 4-37 -
                                        ; переход к общему выходу
         NewInt25:   inc    cs:BusyFlag ; снимает блокировку
                     call   cs:OldInt25 ; диспетчирование реальной прог-
                                        ; раммой
                     jmp    SHORT DiskIOExit1
                                        ; переход к общему выходу
         NewInt26:   inc    cs:BusyFlag ; снимает блокировку
                     call   cs:OldInt26 ; диспетчирование реальной прог-
                                        ; раммой
                     jmp    SHORT DiskIOExit1
                                        ; переход к общему выходу
         DiskIO      ENDP
         ----------------------------------------------------------------

                               Проверка типа дисплея

              Если необходимо,  секция инициализации программы должна про-
         верить тип дисплея и других периферийных устройств. TSR необходи-
         мо знать совсем немного о дисплее,  если она собирается поддержи-
         вать горячие  ключи.  Тип  используемого дисплейного адаптера MDA
         (монохромный дисплейный адаптер)  или  CGA  (цветной  графический
         адаптер)  определить  относительно  просто.  Многие типы дисплеев
         эмулируют либо режим MDA, либо режим CGA. В связи с тем, что типы
         дисплеев  MDA  и  CGA в настоящее время являются наиболее общими,
         ограничим свое обсуждение только этими двумя типами. Как отличить
         дисплей MDA от дисплея CGA, показано в листинге 4-24.

                      Листинг 4-24. Определение типа дисплея
         ----------------------------------------------------------------

         C40         EQU  1              ; дисплей CGA 40 x 25
         C80         EQU  2              ; дисплей CGA 80 x 25
         M80         EQU  3              ; дисплей MDA 80 x 25
         DisplayType DB   0
                     int  11h            ; прерывание проверки аппаратуры
                     and  al,30h         ; выделение видеобитов
                     mov  cl,4           ; сдвиг между битами видеорежима
                     asr  al,cl          ; 0 и 1
                     mov  DisplayType,al ; запоминание видеорежима
         ----------------------------------------------------------------

                          Освобождение операционной среды

             Среда содержит строки символов. Каждая строка имеет имя пере-
         менной,  за которой следует значение (более полное описание среды
         приведено в главе 3).  DOS устанавливает среду при загрузке Вашей
         резидентной подпрограммы TSR.  Программы свободны в интерпретации
         значений этих переменных.  Файл COMMAND.COM использует переменную
         PATH (путь) для указания каталогов, в которых необходимо произво-
         дить поиск загружаемой программы или командного файла.
             Так как при загрузке программы файл  COMMAND.COM  запрашивает
         DOS о  передаче  копии операционной среды,  TSR нет необходимости
         проводить после ее завершения любые изменения в среде.  Время,  в
         течение которого среда является правильной, - это время инициали-
         зации TSR. Использует ли Ваша TSR среду или по разным причинам не
         использует,  зависит от приложения.  В связи с тем, что после за-
         вершения TSR среда является неправильной и она  занимает  опреде-

                                      - 4-38 -
         ленную память,  то  нет  никаких причин хранить ее (хотя отказ от
         освобождения среды не оказывает неблагоприятного  воздействия  на
         TSR).
             Начальный сегмент среды содержится  в  PSP  по  смещению  2Ch
         (смотри листинг 4-15 "Структура PSP"). Для освобождения среды ис-
         пользуйте функцию int 21h AH=49h "Освободить  блок  памяти".  При
         некоторых обстоятельствах  среда отсутствует. Поэтому, прежде чем
         освобождать среду,  необходимо проверить эти обстоятельства. Если
         среда  отсутствует,  то  PSP будет содержать нули по смещению 2Ch
         (функция 49h будет иметь некоторые проблемы при освобождении бло-
         ка памяти в ячейке 00000h).  Пример освобождения среды приведен в
         листинге 4-25.
                         Листинг 4-25. Освобождение среды
         ----------------------------------------------------------------

         ;
         ;                     Освобождение среды
         ;
         ;  Замечание: Структура PSP_W_Envron является частью структуры
         ;             PSP с именем STRUC в листинге 4-15
         FreeEnv  PROC   NEAR
                  pushr  <ax,bx,es> ; сохранение некоторых регистров
                  mov    ah,62h     ; запрос адреса PSP
                  int    21h
                  mov    es,bx      ; es <== PSP
                  xor    ax,ax      ; ax <== 0
                  xchg   ax,es:PSP_W_Envron
                                    ; затирание сегмента среды в PSP
                                    ; ax <== сегмент среды
                  or     ax,ax      ; среда присутствует ?
                  jz     NoEnv      ; если 0 -- нет
                  mov    es,ax      ; es <== среда
                  mov    ah,49h     ; освободить блок памяти
                  int    21h
         NoEnv:   popr   <es,bx,ax> ; восстановление регистров
                  ret               ; возврат
         FreeEnv  ENDP
         ----------------------------------------------------------------


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

             Последней задачей процесса инициализации является вызов функ-
         ции "завершить и оставить резидентной" (int  21h,  функция  31h).
         При  вызове функции "завершить и оставить резидентной" необходимо
         сообщить DOS о том, какой объем памяти нужно сохранить. Для этого
         перед выдачей запроса int 21h AH=31h, следует поместить в регистр
         DX необходимое количество сегментов. DOS освободит все кроме пер-
         вых DX  сегментов памяти,  которыми владела Ваша программа. Обще-
         принято размещать программу инициализации в конце TSR  так, чтобы
         она  могла быть удалена после завершения своей работы.  Следующий
         листинг иллюстрирует использование этой  служебной  функции.  Эта
         программа начинается с адреса (EndOfCode), получаемого округлени-
         ем его до следующей границы сегмента и преобразованием в парагра-
         фы путем деления на 16.


                                      - 4-39 -
                         Листинг 4-26. Выполнение запроса
                        "завершить и оставить резидентной"
         ----------------------------------------------------------------

             mov  dx,OFFSET EndOfCode ; dx <== конец резидентной пр-мы
             add  dx,0fh              ; округление до след. гран. сегм.
             mov  cl,4                ; преобразование смещения в сегм.
             shr  dx,cl               ; dx <== резидентные параграфы
             mov  ah,31h              ; функция TSR DOS
             int  21h
         ----------------------------------------------------------------

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

             Когда TSR  получает  запрос  повторной активации,  она должна
         убедиться в  безопасности  дальнейшего выполнения. Эту задачу уп-
         рощают действия, выполняемые программой инициализации. TSR должна
         проверить флажки критической ошибки и критической секции,  и убе-
         диться  в  том,  что не выполняются непрерываемые операции.  Если
         дальнейшее продолжение выполнения безопасно, TSR должна сохранить
         текущие  значения  регистров,  переключиться  на свой собственный
         стек, установить свои собственные подпрограммы критической ошибки
         и break,  записать информацию, сохраненную в различных глобальных
         переменных DOS и,  наконец,  установить свою  собственную  среду,
         включающую адреса текущих PSP и DTA. Заметим, что очень важен по-
         рядок выполнения этих операций!
             Кроме этих,  могут потребоваться и другие операции. Так, если
         необходимо использовать изображения, TSR должна сохранять в памя-
         ти  содержимое экрана.  Программа PRINT.COM проверяет сводный ре-
         гистр прерывания в контроллере 8259. Если активны какие-либо пре-
         рывания, отличные от прерываний ISR,  которые пробуждают ее,  она
         игнорирует запрос повторной активации. Вероятно, PRINT.COM делает
         эту  проверку,  чтобы  избежать потери символов последовательного
         порта и других устройств отображения.
             После завершения  своей  работы,  TSR должна произвести откат
         выполненных шагов и быстро перейти в фоновый раздел, ожидая снова
         получения  запроса  повторной активации.  TSR должна восстановить
         значения регистров PSP,  DTA, стека и других регистров прерванной
         программы. После того, как TSR завершит эти шаги, она может безо-
         пасно восстановить драйверы обработки критической ошибки  и break
         и осуществить возврат в прерванную программу.

                   Определение безопасности повторной активации

             Невозможно предсказать,  когда произойдет запрос на повторную
         активацию TSR. Секции DOS не являются повторно входимыми, поэтому
         и  TSR не всегда может повторно активироваться при выдаче запроса
         повторной активации.  Детально эта проблема обсуждается в разделе
         "Архитектура DOS". Необходимо с уважением относиться к этим огра-
         ничениям, иначе Ваша программа,  несомненно,  разрушит систему и,
         даже, возможно, разрушит диск.
             Минимальный объем проверок,  который  должна  выполнять  Ваша

                                      - 4-40 -
         TSR,  приведен  в листинге 4-27.  Эта программа предназначена для
         выполнения с запрещением прерываний.  Когда процессор отвечает на
         прерывание,  он запрещает прерывания.  До тех пор, пока программа
         обслуживания прерывания  не  разрешит  прерывания  перед  вызовом
         DOSSafeCheck, Вы не должны неявным образом манипулировать флажком
         прерывания.
             Эта программа начинается с увеличения значения того же самого
         флажка BusyFlag (флажок "занято"),  используемого новой  подпрог-
         раммой  дискового  в/в  (листинг 4-23 "Типовое замещение программ
         ISR дискового в/в"). Этот флажок имеет начальное значение -1. Ес-
         ли инструкция INC в DOSSafeCheck имеет нулевой результат, то про-
         должение выполнения безопасно.  Ненулевой результат означает, что
         выполняется  одна  или более дисковых операций (в конечном счете,
         прерывание int 13h выполняется как результат прерываний int 25h и
         int 26h), или незавершен предыдущий вызов TSR. Так как переключе-
         ние стека выполняется программой повторной активации,  то TSR  не
         является повторно входимой. (Позднее последовательность повторной
         активации будет описана более подробно).
             Затем эта программа проверяет  флажки  критической  секции  и
         критического  прерывания.  Заметим,  что необходимо проверять оба
         флажка. Перед началом обработки критической ошибки драйвер крити-
         ческой  ошибки DOS уменьшает значение флажка критической секции и
         увеличивает значение флажка критической ошибки. Побочным эффектом
         вызова этой подпрограммы является то, что она предохраняет TSR от
         повторного входа.  Перед тем, как TSR переключится в фоновый раз-
         дел,  она должна уменьшить значение флажка "занято" (BusyFlag).

           Листинг 4-27. Определение "безопасности" повторной активации
         ----------------------------------------------------------------

         BusyFlag:   DB    -1          ; флажок "занято"
         CSectFlg    DW    0,0         ; здесь при инициализации запоми-
                                       ; нается адрес флажка критической
                                       ; секции
         CErrFlg     DW    0,0         ; здесь при инициализации запоми-
                                       ; нается адрес флажка критической
                                       ; ошибки
         DOSSafe     PROC  NEAR
         DOSNotSafe:
                     stc               ;;; индикация, что "небезопасно"
                     ret               ;;; и возврат
         DOSSafeCheck:
                     inc   cs:BusyFlag ;;; попытка снять блокировку
                     jg    DOSNotSafe  ;;; если больше -- то некоторые
                                       ;;; уже имеют блокировку
                     pushr <ds,si,ax>  ;;; сохранение, т.к. мы можем по-
                                       ;;; лучить при INDosFlag
                     lds   si,DWORD PTR cs:CSectFlg ;;; ds:si <== адрес
                                       ;;; флажка критической секции
                     lodsb             ;;; al <== значение флажка крити-
                                       ;;; ческой секции
                     lds   si,DWORD PTR cs:CErrFlg ;;; ds:si <== адрес
                                       ;;; флажка критической ошибки
                     or    al,BYTE PTR [si] ;;; вычисление ненулевого
                                       ;;; флажка критической ошибки
                     popr  <ax,si,ds>
                     jnz   DOSNotSafe  ;;; если не 0, то либо критичес-

                                      - 4-41 -
                                       ;;; кая ошибка, либо int 21
                     clc               ;;; индикация, что "безопасно"
                     ret               ;;; и возврат
         DOSSafe     ENDP
         ----------------------------------------------------------------

                     Переключение стека и сохранение регистров

             Стек является важной составной частью  среды  программы.  Так
         как  повторная  активация происходит в результате прерывания,  то
         способ определения используемого стека или доступного  пространс-
         тва стека отсутствует. Стеки, используемые диспетчером прерывания
         int 21h,  достаточно большие,  чтобы разместить значения всех ре-
         гистров процессора. Любая прерванная программа также должна иметь
         возможность использовать оставшуюся память стека,  иначе  она  не
         сможет  выполнять запросы BIOS.  Диспетчер BIOS сохраняет все ре-
         гистры в текущем стеке.
             Перед повторной активацией TSR,  программа ISR должна  сохра-
         нить  все  регистры  и переключиться на личный стек TSR.  Разумно
         сохранять регистры в том стеке,  который использовался  в  момент
         возникновения прерывания. Оба значения стека и регистров являются
         частью одного и того же контекста программы,  и стек должен иметь
         необходимое пространство для этих значений.

                 Организация "ловушек" break  и критических ошибок

             Следующий шаг в последовательности активации связан с измене-
         нием информации состояния,  которую DOS записала относительно те-
         кущей программы.  В этой точке Ваша TSR теперь становится текущей
         программой.  Так как критические ошибки и критические break могут
         завершить текущую программу, необходимо быть уверенным в том, что
         Вы  получили  шанс  вернуться обратно и нашли способ как это сде-
         лать.  Установка своих собственных драйверов критической ошибки и
         break  позволяет TSR быть аккуратной в таких случаях и обходиться
         с ними безопасным способом.
             В связи  с  отсутствием  способа определения из программы ISR
         текущей программы переднего плана,  установка драйверов критичес-
         кой ошибки  и  break является искусным  приемом программирования.
         Если для манипуляции входом IVT используется int 21, то существу-
         ет определенный  риск  получения ошибки break .  Самым безопасным
         способом является способ манипуляции входами IVT непосредственно.
         Заметим, что необходимо запретить break , пока производится изме-
         нение входов таблицы.  Хотя это и не  кажется  очевидным,  другие
         программы могут прервать Вашу программу в середине выполнения из-
         менений, и могут модифицировать входы IVT, с которыми Вы работае-
         те. Как выполнить эту задачу, показывает листинг 4-28.
             Действия, которые будут выполняться  во  вновь  установленных
         драйверах break и критической ошибки,  зависят от TSR.  Они могут
         быть проигнорированы,  но,  обычно,  программа при  возникновении
         критической ошибки должна выполнить какие-либо действия. Если TSR
         может иметь дело со сбойными запросами int 21h (практически,  не-
         обходимо  проверять  результаты выполнения каждого запроса и быть
         готовым иметь дело с ошибками),  то простейшим способом обработки
         является  отказ  от вызова.  В то же время имеются другие способы
         обработки. Например, если произошел сбой диска в связи с тем, что
         была открыта дверца накопителя,  то необходимо напечатать сообще-
         ние и повторить операцию.

                                      - 4-42 -
            Листинг 4-28. "Ловушка" критических ошибок и break  из ISR
         ----------------------------------------------------------------

         IVT         SEGMENT AT 00h     ; отметить абсолютный адрес
                     ORG     23h*4      ; мы не заботимся относительно
                                        ; 0 - 22h
         IVT23       DW      0,0        ; входы ссылок для int 23h и
         IVT24       DW      0,0        ; int 24h
         IVT         ENDS
         _text       SEGMENT BYTE PUBLIC 'code'
         OldInt23    DW      0.0        ; здесь мы будем сохранять адре-
         OldInt24    DW      0,0        ; са критической ошибки и преры-
                                        ; вания
                     ASSUME  ds:_text
         BKGNewErrHndlr PROC NEAR
                     pushr   <ax,di,si,ds,es>  ; сохранение всех изменяе-
                                        ; мых регистров
                     cld                ; вывод флажка направления в из-
                                        ; вестное состояние для movsw и
                                        ; stosw
                     mov     ax,cs      ; es указывает на сегмент, содер-
                     mov     es,ax      ; жащий OldInt23
                     xor     ax,ax      ; ds указывает
                     mov     ds,ax      ; на IVT
                     ASSUME  ds:IVT,es:_text ; сообщить MASM, что ожидание
                     mov     si,OFFSET IVT23 ; установить копию IVT
                     mov     di,OFFSET OldInt23 ; входы с movsw
                     mov     cx,4       ; каждый вход 2 слова
                     cli                ; В А Ж Н О !!!
                     rep     movsw      ;;; копирование текущих входов
                                        ;;; ivt
                     mov     es,ax      ;;; es теперь указывает на IVT
                     ASSUME  es:IVT     ;;; сообщить MASM об изменении
                     mov     ax,OFFSET NewInt23 ;;; ввод новых значений
                     stosw              ;;; в IVT
                     mov     ax,cs
                     stosw
                     mov     ax,OFFSET NewInt24
                     stosw
                     mov     ax,cs
                     stosw
                     sti
                     popr    <es,ds,si,di,ax>
                     ASSUME  ds:_text
                     ret
         BKGNewErrHndlr      ENDP
         NewInt23    PROC    FAR        ; новый обработчик break
                     iret               ; игнорирование break
         NewInt23    ENDP
         NewInt24    PROC    NEAR       ; новый обработчик критической
                                        ; ошибки
                     iret               ; вероятно будут выполнены ка-
                                        ; кие-либо действия относительно
                                        ; ошибки (может быть отказ вызова)
         NewInt24    ENDP
         _text       ENDS
         ----------------------------------------------------------------

                                      - 4-43 -

                         Обращение к глобальным переменным

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

                  Листинг 4-29. Обращение к глобальным переменным
         ----------------------------------------------------------------

         BKGDTA    DB   80h DUP(0) ; минимальный размер DTA
         BKGPSP    DW   0          ; программа инициализации запоминает
                                   ; здесь значение PSP
         DOSPSP    DW   0          ; здесь мы будем сохранять PSP и DTA
         DOSDTA    DW   0,0        ; прерванной программы
         BKGSetPSP PROC NEAR
                   pushr <ax,bx>   ; сохранение изменяемых регистров
                   mov  ah,62h     ; запрос DOS о текущем PSP
                   int  21h
                   mov  DOSPSP,bx  ; сохранение текущего PSP
                   mov  ah,50h     ; сообщение DOS об использовании но-
                                   ; вого PSP
                   mov  bx,BKGPSP
                   int  21h        ; недокументирована
                   popr  <bx,ax>
                   ret
         BKGSetPSP ENDP
         BKGSetDTA PROC NEAR
                   pushr <ax,bx,dx,es>
                   mov  ah,2fh
                   int  21h
                   mov  DOSDTA,bx  ; запись адреса DTA
                   mov  DOSDTA+2,es
                   lea  dx,BKGDTA  ; ds:dx <== новая DTA
                   mov  ah,1ah
                   int  21h
                   popr <es,dx,bx,ax>
                   ret
         BKGSetDTA ENDP
         ----------------------------------------------------------------

               Фоновая обработка с использованием прерывания int 28h

             Финальной частью  изучения TSR является фоновая обработка.Эта
         возможность является недокументированной,  и поэтому недостаточно
         понятной. При  правильном использовании,  пока выполняется другая
         программа,TSR может выполнять запросы к BIOS. Программа PRINT.COM

                                      - 4-44 -
         использует эту возможность для чтения блоков файла.  Система под-
         готовки текстов может использовать эту возможность для сохранения
         файла  параллельно с редактированием переднего плана,  а средство
         ведения электронных таблиц при фоновой обработке  может выполнять
         длинные вычисления.
             DOS обеспечивает в помощь программисту  некоторые  "зацепки",
         но для их использования ему необходимо довольно много поработать.
         Такими "зацепками" для фоновой обработки являются  программы  ISR
         критической секции,  критической ошибки и int 28h. Довольно часто
         программы много времени расходуют на ожидание  ввода  информации.
         Путем захвата прерывания int 28h TSR может использовать для своих
         целей циклы центрального процессора,  которые иначе бы  тратились
         на ожидание ввода информации.  В связи с тем, что эту возможность
         также могут использовать и другие программы TSR,  то ISR прерыва-
         ния int 28h, когда она выполняется, должно образовывать цепочку с
         предыдущей ISR.
             ISR прерывания int 28 запускает TSR только в том случае, если
         прикладная программа переднего плана использует функцию  DOS  в/в
         символов. TSR, которая нуждается в фоновой работе, обычно "захва-
         тывает", кроме того, одно или два прерывания таймера. ISR таймера
         обеспечивает TSR доступ к процессору даже в том случае, если при-
         оритетная прикладная программа является программой с интенсивными
         вычислениями, или не использует функции в/в символов.
             Написание ISR int 28h является достаточно простым делом.  Но-
         вая  ISR сначала вызывает старую ISR и затем увеличивает значение
         того же самого флажка BusyFlag (флажок  "занято"),  используемого
         программами ISR прерываний int 8, int 9 и дискового в/в. Если ре-
         зультат не нулевой,  то выполняются некоторые непрерываемые функ-
         ции.  Так  как  эта  ISR должна получать управление только тогда,
         когда доступ к диску безопасен,  увеличение должно всегда выраба-
         тывать нулевой результат. Тем не менее, необходимо быть готовым к
         блужданиям по прерываниям int 28h.  После повторной активации TSR
         программа ISR уменьшает значение флажка BusyFlag и возвращает уп-
         равление в DOS.  Заметим,  что не нужно выполнять проверку флажка
         критической ошибки:  поскольку выполняется запрос int 21h,  то Вы
         знаете,  что он установлен;  однако он всегда сохраняется для вы-
         полнения запросов int 21h,  номер функции которых больше значения
         0ch.  Для предохранения TSR от повторной  активации  при  нажатии
         горячего ключа  или  при  прерывании таймера необходимо увеличить
         значение флажка BusyFlag.

            Листинг 4-30. Подпрограмма обслуживания прерывания int 28h
         ----------------------------------------------------------------

         OldInt28   DD         0      ; здесь программа инициализации за-
                                      ; писывает адрес старой ISR
         BusyFlag   DB         -1     ; защита нереентерабельных секций
                                      ; программы
         Int28ISR   PROC      FAR
         Int28Exit0:
                    dec  cs:BusyFlag  ; освобождение Вашей блокировки и
                    iret              ; возврат
         NewInt28:
                    pushf             ; имитация прерывания
                    call cs:OldInt28  ; диспетчирование оригинальной
                                      ; программы
                    inc  cs:BusyFlag  ; попытка снять блокировку

                                      - 4-45 -
                    jg   Int28Exit0   ; если больше -- есть непрерываемые
                    call BKGResume    ; диспетчирование фоновой задачи
                    dec  cs:BusyFlag  ; снятие блокировки
                    iret              ; и возврат
         Int28ISR   ENDP
         ----------------------------------------------------------------

             Процесс повторной активации TSR довольно прост: сохраните все
         регистры  в  текущем  стеке  и  переключитесь на личный стек TSR.
         Большинство фоновых TSR выполняется короткое время и затем  пере-
         водят  сами  себя в состояние ожидания.  Обычно они сохраняют ре-
         гистры  в своих собственных стеках и возвращают управление в пре-
         рванную  программу.   Как   часть   последовательности  повторной
         активации восстанавливайте  регистры,  сохраненные  стеками  TSR,
         когда они переводятся в состояние ожидания.
             Программа PRINT.COM  во  время  выполнения последовательности
         действий при  своей  повторной  активации,  увеличивает  значение
         флажка критической секции. Эта утилита выполняет довольно необыч-
         ные действия.  Она обходит DOS и непосредственно вызывает драйвер
         устройства печати. Вероятно, увеличение значения флажка критичес-
         кой секции устраняет проблемы возможного повторного входа в драй-
         вер устройства.  Если Ваша TSR осуществляет непосредственный  до-
         ступ к драйверу, то,  по всей видимости,  очень полеэно подражать
         действиям программы PRINT.COM.
             Далее, установите свои собственные драйверы прерывания и кри-
         тической ошибки, сделайте текущим PSP для Вашей TSR, и переключи-
         тесь на личную DTA.   TSR, из которой была выбрана следующая под-
         программа, поддерживает как активацию горячего ключа, так и акти-
         вацию фоновой обработки.  Если повторная активация выполняется  в
         ответ на нажатие горячего ключа, то необходимо сохранить содержи-
         мое текущего изображения на экране дисплея и выключить  из работы
         буфер опережающего  ввода информации с клавиатуры.  Здесь сделано
         предположение о том, что любые клавиши в буфере опережающего вво-
         да информации  с  клавиатуры   были нажаты для предыдущей текущей
         программы и внесут только путаницу в повторную активацию TSR. Ин-
         струкция возврата управления передает управление TSR. По заверше-
         нии работы TSR будет вызывать подпрограмму BKGSuspend  (перевод в
         состояние ожидания для фоновой работы).
             Коды, обеспечивающие ожидание,  немного странны. TSR, исполь-
         зующая эти коды, периодически вызывает BKGSuspend. При определен-
         ных условиях BKGSuspend отправляет TSR на задний план, а в других
         случаях она ничего не делает. Пользователь может пожелать активи-
         зировать  TSR с заднего плана путем нажатия горячего ключа.  Если
         TSR,  выполняющаяся  на  переднем  плане,  вызывает  подпрограмму
         BKGSuspend, то эта подпрограмма проверяет буфер опережающего вво-
         да информации с клавиатуры на наличие особой  клавиши  (BGCombo),
         нажатие которой отправляет ее на задний план.  Если в буфере этой
         клавиши нет, то подпрограмма BKGSuspend игнорирует запрос на при-
         остановку.  Если клавиша BGCombo в буфере обнаружена,  или если в
         настоящий момент TSR выполняется на заднем плане, то она переста-
         ет быть активной.
             Приостановка выполняется по шагам, аналогично активации, но в
         обратном порядке.  Подпрограмма BKGSuspend восстанавливает сохра-
         ненные DTA и PSP, восстанавливает драйверы прерывания и критичес-
         кой  ошибки,  сохраняет  текущие регистры,  восстанавливает экран
         (SCRBackground),  уменьшает значение флажка  критической  секции,
         переключает стеки,  восстанавливает индексные регистры и изменяет

                                      - 4-46 -
         переменную  PGMState.  Инструкция  RET   в   конце   подпрограммы
         BKGSuspend возвращает управление в ISR, которая активизирует TSR.
         Если  TSR  выполнялась   на   заднем   плане,   то   подпрограмма
         SCRBackground не выполняет переключение экрана.

                    Листинг 4-31. Перевод в состояние ожидания
                            и возобновление работы TSR
         ----------------------------------------------------------------

         SuspendResume      PROC    NEAR
         AltF10      EQU    113           ; расширенный код ASCII для
                                          ; клавиши ALT F10
         BGCombo     EQU    AltF10 SHL 8  ; LSB расширенного ASCII=0
         BKG_C_FG    EQU    1
         BKG_C_BG    EQU    2
         SaveStack   STRUC
         rSP         DW     0
         rSS         DW     0
         SaveStack   ENDS
         switch      MACRO  sstack,dstack ;; переключение стеков
                     cli                  ;; запрещение прерывания во
                                          ;; время переключения стеков
                     mov    sstack.rSS,SS ;; запись текущего стека
                     mov    sstack.rSP,SP
                     mov    SS,dstack.rSS ;; установка нового стека
                     mov    SP,dstack.rSP
                     sti                  ;; разрешение прерываний
                     ENDM
         _text       SEGMENT BYTE PUBLIC 'code'
         PgmState    DB     0             ; сохранение дорожки состояния
                                          ; программы
         InDosFlag   DD     0             ; здесь программа инициализации
                                          ; сохраняет адрес флажка крити-
                                          ; ческой секции
         OldStack    SaveStack <>         ; стек прерванной программы
         BKGStack    SaveStack <>         ; стек TSR. Устанавливается
                                          ; программой инициализации
         BKGResume:
                     call   BKGSaveAll    ; сохранение всех регистров в
                                          ; текущем стеке
                     cld                  ; флаг начального направления
                     mov    ax,cs
                     mov    ds,ax         ; ds <== программный сегмент
                     switch OldStack,BKGStack ; переключение на стек
                                          ; заднего плана
                     call   BKGRestoreAll ; восстановление регистров
                                          ; заднего плана
                     pushr  <es,di>
                     les    di,InDosFlag  ; es:di <== флаг входа в DOS
                     inc    BYTE PTR es:[di] ; установка флага входа в
                                          ; DOS
                     popr   <di,es>
                     call   BKGNewErrHndlr ; установка своих собственных
                                           ; драйверов критической ошиб-
                                           ; ки и прерывания
                     call   BKGSetPSP      ; изменение PSP
                     call   BKGSetDTA      ; изменение DTA

                                      - 4-47 -
                     cli
                     cmp    PopupPending,0 ;;; ожидание popup?
                     jz     _br0           ;;; если 0 -- нет
                     dec    PopupPending   ;;; уменьшение на 1
                     mov    PgmState,BKG_C_FG ;;; перевод программы на
                                           ;;; передний план
                     call   SCRForeground  ;;; перевод экрана
                     call   BKGBufFlush    ;;; выключение буфера клавиа-
                                           ;;; туры
         _br0:       sti
                     ret
         BKGSuspend:
                     cmp    PgmState,BKG_C_FG ; выполнение на переднем
                                              ; плане?
                     jl     _bs0              ; если меньше -- задний
                                              ; план
                     jg     _bs2              ; если больше -- инициали-
                                              ; зация (игнорирование
                                              ; приостановки)
         ;
         ; Текущее выполнение на переднем плане. Проверка нажатия
         ;                           клавиши
         ;
                     push   ax             ; сохранение текущего значе-
                                           ; ния ax
                     xor    ah,ah          ; ah <== 1 (проверка состояния
                     inc    ah
                     int    16h            ; выдача запроса
                     jz     _bs1           ; если 0 -- нет доступного
                                           ; символа
                     cmp    ax,BGCombo     ; это символ заднего плана?
                     jnz    _bs1           ; если не 0 -- нет
                     xor    ah,ah          ; ah <== 0 (запрос чтения)
                     int    16h            ; удаление символа из буфера
                     pop    ax             ; восстановление ax
         ;
         ; Выполнение на заднем плане и запрос приостановки.
         ;
         _bs0:       call   BKGRestoreDTA  ; восстановление DTA
                     call   BKGRestorePSP  ; восстановление PSP
                     call   BKGRestoreErrHndlr ; восстановление старых
                                           ; драйверов критической ошиб-
                                           ; ки и прерывания
                     call   SCRBackground  ; восстановление экрана
                     call   BKGSaveAll     ; сохранение регистров зад-
                                           ; него плана
                     les    di,InDosFlag   ; es:di <== адрес входа в DOS
                     dec    BYTE PTR es:[di] ; уменьшение флажка входа в
                                           ; DOS
                     switch BKGStack,OldStack ; изменение стеков
                     call   BKGRestoreAll  ; восстановление регистров
                     mov    cs:PgmState,BKG_C_BG ; программы на заднем
                                           ; плане
                     ret                   ; возврат
         _bs1:       pop    ax             ; восстановление начального
                                           ; значения ax
         _bs2:       ret                   ; возврат

                                      - 4-48 -
         SuspendResume      ENDP
         _text              ENDS
         ----------------------------------------------------------------


                              Удаление TSR из памяти

             В связи с ограниченным размером физической памяти  персональ-
         ного  компьютера  может  возникнуть необходимость удаления TSR из
         памяти, когда она станет ненужной. Процесс удаления TSR из памяти
         не представляет большого труда, однако, имеются некоторые пробле-
         мы.  Очень часто TSR захватывает векторы прерываний и перед осво-
         бождением памяти необходимо восстановить эти векторы.
             При инициализации TSR должна  записать  начальное  содержимое
         векторов, которые она будет изменять в процессе своей работы. Ес-
         ли отсутствует другая TSR,  загружаемая после захвата этих векто-
         ров,  то  можно восстановить эти векторы прерываний в их первона-
         чальные  значения  и  освободить  память,  занимаемую  TSR.  Если
         интересующие  Вас  векторы  прерываний  все еще указывают на Вашу
         программу, то это является надежным подтверждением того, что дру-
         гая программа TSR не захватила их. Но, допустим, что Ваша TSR ис-
         пользует вектор прерывания совместно с другой TSR, которая загру-
         жается  после  нее.  Тогда,  каждая  TSR  должна иметь записанное
         первоначальное содержимое вектора и вставленный вход IVT,  указы-
         вающий на свою собственную программу.  Существующий вход IVT ука-
         зывает на TSR, загруженную последней, которая должна иметь сохра-
         ненный вектор для первой TSR.  Первая TSR, в свою очередь, должна
         иметь сохраненный вектор для исходной ISR.
             Если текущий  вход  IVT замещается сохраненным значением,  то
         тем самым из цепочки ISR эффективно удаляется  другая  TSR.  Если
         вторая TSR могла быть введена только с помощью этого единственно-
         го вектора,  то все, что Вы получите, будет потеря  памяти.  Если
         вторая  TSR  имеет другую точку входа и пытается включиться в це-
         почку к сохраненному вектору прерывания, который изменен Вами, то
         эта ссылка будет указывать на незанятый блок памяти.
             Наиболее чистым решением этой  проблемы  является  разработка
         TSR,  которая  управляет другими TSR.  Имеется превосходный пакет
         общего назначения  Mark/Release  (отметить/освободить),   который
         доступен из многих информационных источников.TSR Mark (отметить),
         выполняемая перед другими программами, загружается и делает копию
         IVT,  а  также  записывает  текущее состояние памяти.  Другие TSR
         пользователь загружает  по  мере  необходимости.  Выполнение  TSR
         Release  (освободить) восстанавливает память и таблицу IVT значе-
         ниями, записанными с помощью программы Mark. Возможна вложенность
         вызовов программы Mark.
             Программы Mark/Release работают в большинстве,  но не во всех
         случаях. Они перезапоминают IVT и память. Если TSR изменила неко-
         торую другую структуру данных DOS, то после удаления TSR из памя-
         ти эта структура данных так и останется измененной.

                                    Заключение

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




                Глава 5. ПРОГРАММЫ РЕАЛЬНОГО ВРЕМЕНИ В СРЕДЕ MS-DOS

               Обзор программ реального времени
               Использование MS-DOS для приложений реального времени
               Проектирование систем реального времени в MS-DOS
               Многозадачность в MS-DOS
               Резюме
               Библиография

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

                         Обзор программ реального времени

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

                             Что такое реальное время?

              Для понимания того,  что такое реальное время,  предлагается
         рассмотреть простой пример системы  cоздания  фильма,  в  котором
         компьютер используется для создания видеоэффекта.  Система созда-
         ния фильма может воспроизводить кадр  движущегося  фильма  каждые
         несколько  минут.  Это будет давать эффект сверхмедленного движе-
         ния.  Но, в идеале, движение будет производится на полной скорос-
         ти,  что позволит зрителю видеть его. Если компьютер будет произ-

                                      - 5-2 -
         водить 24 кадра в секунду, что соответствует скорости их движения
         в фильме,  то кадры будут появляться в нерастянутом масштабе вре-
         мени, или в "реальном времени".
              Операционная система в реальном времени - это система, кото-
         рая:

         - Обеспечивает  прямое обращение к внешнему окружению компьютера.
         - Достаточно оперативно отслеживает внешнее окружение.

             При таком понимании реального времени, область применения ре-
         ального времени в широком смысле включает все компьютерные систе-
         мы.  Например, система с пакетной обработкой может быть выполнена
         в реальном времени,  если критерий времени ответа достаточно  ве-
         лик.
              Если внутренняя  налоговая инспекция объявляет,  что возврат
         чеков будет производится в течении 60 дней,  и если компьютеры  с
         пакетной обработкой могут управлять этой работой,  они работают в
         системе реального времени. Однако, термин "системы реального вре-
         мени",  используемый  в  специальной литературе,  обычно включает
         только системы с временем реакции порядка секунд или меньше. Сис-
         темы реального времени в этом смысле и будут освещены в этой гла-
         ве.  Вывод: когда компьютер используется для обращения к имеющему
         определенные ограничения по времени (обычно меньше, чем несколько
         секунд) внешнему окружению,  которое должно работать  безотказно,
         то это обеспечивается системой реального времени.
              Системы с пакетной обработкой,  диалоговые системы и системы
         с разделением времени не являются  системами  реального  времени,
         так как  они не гарантируют абсолютного ограничения времени реак-
         ции.  Например,  системы с разделением времени обычно  достаточно
         быстры для диалогового использования,  но они могут работать дос-
         таточно медленно и одна пользовательская задача может блокировать
         другую.

                      Характеристики систем реального времени

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

                      Основные типы систем реального времени

              Системы реального времени разделяются на три типа на основа-
         нии направления потока данных, как показано на рис. 5-1:
              - Однонаправленные
              - Двухнаправленные стабильные
              - Двухнаправленные потенциально нестабильные
              Любая система реального времени может быть представлена  од-
         ним из этих трех основных типов.
                         ЪДДДДДДДДДДДДДДДД¬
                         ¦ Системы реаль- ¦
                         ¦  ного времени  ¦
                         АДДДДДДДДВДДДДДДДЩ
                                  ¦
                   ЪДДДДДДДДДДДДДДБДДДДДДДДДДДДДД¬
                   ¦                             ¦
          ЪДДДДДДДДБДДДДДДДД¬           ЪДДДДДДДДБДДДДДДДД¬
          ¦                 ¦           ¦                 ¦
          ¦ Однонаправленные¦           ¦ Двухнаправленные¦
          АДДДДДДДДДДДДДДДДДЩ           АДДДДДДДДДВДДДДДДДЩ
                                                  ¦
                                   ЪДДДДДДДДДДДДДДБДДДДДДДДДДДДДД¬
                                   ¦                             ¦
                          ЪДДДДДДДДБДДДДДДДД¬           ЪДДДДДДДДБДДДДДДДД¬
                          ¦    Стабильные   ¦           ¦   Потенциально  ¦
                          ¦                 ¦           ¦   нестабильные  ¦
                          АДДДДДДДДДДДДДДДДДЩ           АДДДДДДДДДДДДДДДДДЩ


                     Рис. 5-1. Типы систем реального времени,
                      основанные на направлении потока данных

                             Однонаправленные системы

              Однонаправленными считаются системы,  в которых поток данных
         имеет только одно направление, то есть или из внешнего устройства
         в компьютер,  или из компьютера во внешнее устройство, но не то и
         другое.  Это главным образом системы генерации или сбора  данных.
         Приведенная ранее система генерации кинокартины является примером
         системы генерации данных.  Единственным требованием является  то,
         что  кадры должны воспроизводиться с частотой 24 кадра в секунду.
         Это означает,  что генерация кадра не должна занимать больше 1/24
         секунды.  Даже незначительная задержка будет заметна и неприемле-
         ма. Если большинство кадров могут быть воспроизведены за 1/24 се-
         кунды, а некоторые займут немного больше времени, тогда для полу-
         чения эффекта реального времени может быть использован буфер, как
         показано на рис. 5-2. На самом деле компьютер может воспроизвести
         несколько  кадров перед началом отображения. Новые кадры заносят-
         ся  в буфер  по мере  генерации,  и один кадр из буфера отобража-
         ется каждую 1/24 часть секунды.  Даже если компьютеру  необходимо
         больше  времени для воспроизведения одного кадра,  это допустимо,
         так как вывод из буфера воспроизводится только 24 раза в секунду.


                                      - 5-4 -
            ЪДДДДДДДДДДДДДД¬       ЪДДДДДДДДДДДДДД¬       ЪДДДДДДДДДДДДДД¬       Ъ
            ¦ Генерирующий ¦       ¦              ¦       ¦              ¦       ¦
            ¦     кадры    ГДДДДДДДґ     Буфер    ГДДДДДДДґ   Дисплей    ¦
            ¦   компьютер  ¦       ¦              ¦       ¦              ¦       ¦
            АДДДДДДДДДДДДДДЩ       АДДДДДДДДДДДДДДЩ       АДДДДДДДДДДДДДДЩ       А

                Рис. 5-2. Генерация данных с использованием буфера

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

                        Двухнаправленные стабильные системы

              Однонаправленные системы  осуществляют  ввод  или  вывод  из
         компьютера,  но не то и другое одновременно.  Однако,  для многих
         систем реального времени, необходим и ввод  и вывод. Такие систе-
         мы называются двухнаправленными системами. Эти системы могут быть
         стабильными и потенциально нестабильными. Давайте сначала обсудим
         двухнаправленные стабильные системы на примере системы управления
         домом.
              Система управления домом может быть использована для  управ-
         ления отоплением, вентиляций, кондиционированием, освещением, по-
         ливом лужаек и т.д.  Для поддержания определенной температуры не-
         обходимо  ее  измерять  и  корректировать каждые несколько минут.
         Температура остается приемлемой,  даже если она остается неизмен-
         ной 10 или 20 минут. Таким образом, случайная задержка не являет-
         ся катастрофой, так как температура какое-то время удерживается и
         система остается под контролем.  Такая система считается стабиль-
         ной.  Другим примером является автоматический  кассовый  аппарат.
         Большинство транзакций занимает всего несколько секунд, но допус-
         тимо,  если отдельная транзакция длится дольше из-за  перегрузки.
         Заметим,  что здесь не фиксируется абсолютное ограничение времени
         (т.е., что ответ должен приходить в течении х секунд). Но ограни-
         чение времени  все  же имеется (т.е.  покупатель будет раздражен,
         если не получит деньги через 5 минут после запроса).

                Ддухнаправленные потенциально нестабильные системы

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

                    Типичные временные характеристики и решения
                             систем реального времени

              Для понимания того,  как быстро внешнее  устройство  требует
         ответа,  наиболее важной временной характеристикой является время
         ответа. Время ответа - это время между вводом в компьютерную сис-
         тему  и  завершением  ею обработки или пересылки ответа на вывод.
         Таким образом,  это полное время,  в течение которого  транзакция
         или активная задача остается в вычислительной системе.
              Например, если  система  автоматических  кассовых  аппаратов
         (АКА) имеет только один терминал,  тогда время ответа есть просто
         время от того момента, когда покупатель делает заявку, до полного
         выполнения ее компьютером.  Однако,  в действительности,  система
         АКА имеет много терминалов. Поэтому время ответа включает задерж-
         ки,  вызванные тем, что компьютер обрабатывает несколько запросов
         одновременно.
              Диапазон времен  ответов для некоторых приложений показан на
         рисунке 5-3. Для АКА или систем резервирования билетов на авиали-
         нии время ответа должно быть порядка секунд.  Для некоторых спра-
         вочных систем базы данных допустимо время ответа до 20  секунд. С
         другой стороны, время ответа для таких приложений, как управление
         полетом ракеты,  радарное слежение и сбор научных данных,  должно
         измерятся в миллисекундах.

                               ¦  ¬
                      0,1 мсекД†Д ¦    Радарный поиск
                               ¦  Щ ¬
                        1 мсекД†Д   ¦
                               ¦    |  Печать на пишущей машинке
                       10 мсекД†Д   |
                               ¦   |
                      100 мсекД†Д  |   Ввод данных экспериментов
                               ¦   ¦
                         1 секД†Д  Щ ¬
                               ¦   ¬ ¦ Банки,системы резервирования, кассы
                        10 секД†Д ¬¦ Щ
                               ¦  ¦|   Запросы к базам данных
                       100 секД†Д ¦|
                               ¦  ¦    Система управления домом
                      1000 секД†Д ¦
                               ¦  Щ
                     10000 секД†Д

                    Рис. 5-3. Интервалы типичных времен ответа

              Другой важной временной характеристикой  является  временной
         интервал между двумя транзакциями. Он определяет, как часто могут
         обрабатываться транзакции.  Временной интервал может быть случай-

                                      - 5-6 -
         ным или периодическим.  Он случаен,  когда определяется событиями
         во  внешнем  устройстве  (таким,  как нажатие клерком клавиши или
         прерывание от какого-либо устройства).  Оно  периодическое,  если
         определяется часами или каким-либо другим устройством в компьюте-
         ре. При определении требуемого временного интервала  надо  учиты-
         вать максимально возможную в любое время загрузку. Иначе в период
         пика загрузки могут быть потеряны данные, что неприемлемо.
              Подобно времени ответа, временной интервал может  изменяться
         от частей миллисекунды (или меньше), до нескольких минут.Разветв-
         ленная система сберегательных банков в  напряженное  время  ленча
         может иметь одну транзакцию в секунду. Оператор может печатать от
         5 до 10 символов в секунду. Система сканирования радара может по-
         сылать данные каждую миллисекунду. Справочная система базы данных
         поддерживает только случайные запросы.
              На таблице 5-1 приводятся типы реализаций, обычно используе-
         мые для приложений в реальном времени, в зависимости от запрошен-
         ного времени ответа. Заметим, что MS-DOS не включена в эту табли-
         цу,  так как не является общепринятой для приложений  в  реальном
         времени.

                                                         Таблица 5-1
                  Времена ответа и общепринятые реализации систем
                                 реального времени
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Диапазон времен   ¦     Общепринятые реализации реального
              ответа       ¦                времени
         ДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          10 нсек-100 нсек ¦Выделенная логика ECL, фиксированная программа
         100 нсек - 1 мксек¦Выделенная стандартная логика, программируемая
           1    - 100 мксек¦Быстрый процессор с выделенной программой
         100 мксек- 1 мсек ¦Микропроцессор с ядром в реальном времени
           1 мсек - 1 сек  ¦Микропроцессор в реальном времени
           1 мсек - выше   ¦Все какие угодно
         ДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

               Использование MS-DOS для приложений реального времени

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

                               Быстродействие MS-DOS

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

                              Тактовая частота MS-DOS

              Во время написания этой книги   MS-DOS  доступна  для  шести
         процессоров семейства 8086: 8088, 8086, 80188, 80286 и 80386. Са-
         мый медленный из них 8088,  а самый быстрый - 80386 (и самый мощ-

                                      - 5-7 -
         ный). Процессорам семейства 8086 доступна различная тактовая час-
         тота,  как показано в таблице  5-2.  Тактовая  частота  4.77  МГц
         означает, что генерируется 4.77 миллиона тактов в секунду. Такто-
         вая частота определяет быстродействие процессора:  чем она  выше,
         тем быстрее  процессор. Заметим однако, что процессор 80286 быст-
         рее, чем 8086, имеющий такую же тактовую частоту.
              В связи с тем, что процессор 8088 самый медленный из семейс-
         тва 8086,  все временные характеристики в этой главе  даются  для
         процессора 8088,  имеющего тактовую частоту 4.77  МГц.  Если  Ваш
         компьютер имеет большее быстродействие, Вы сможете получить ответ
         быстрее.  Так как семейство процессоров  8086  имеет  возможность
         предварительной выборки команд из очередей,  действительное время
         выполнения некоторой последовательности команд  может  отличаться
         от времени выполнения, суммированного из отдельных команд.

                                                         Таблица 5-2
                      Тактовая частота для MS-DOS компьютеров
         ДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДД
                         ¦                           ¦
         Процессоры 8086 ¦ Наименование компьютера   ¦Тактовая частота
         ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДД
                         ¦                           ¦
           8088          ¦ IBM PC                    ¦  4.77,8 МГц
           8086          ¦ IBM PC Compatible         ¦  8,12 МГц
           80188/186     ¦ IBM PC Compatible         ¦  8,10 МГц
           80286         ¦ IBM PC AT (режим 8086)    ¦  8,10 МГц
           80386         ¦ IBM PC AT (386) (режим 8086) 16,20,25 МГц
         ДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДД

              MS-DOS поддерживает  часы  в реальном масштабе времени.  Они
         обеспечивают дату и время и доступны любой программе.  Время под-
         держивается  с точностью до 10 миллисекунд.  Таким образом,  если
         для приложения необходима разрешающая способность меньше 10  мил-
         лисекунд, оно не может использовать эти часы.
              Следующая программа считывает дату из MS-DOS. Для считывания
         даты программное обеспечение использует прерывание 21h.  В  дейс-
         твительности, прерывание 21h используется при запросе любого обс-
         луживания MS-DOS, при этом регистр AH должен содержать код выпол-
         няемой  функции.  Позднее мы обсудим несколько примеров программ,
         использующих  программные  прерывания  для  вызова   MS-DOS   или
         POM-BIOS.

              mov     ah,2ah       ; считывание даты
              int     21h          ; вызов DOS
              mov     year,cx      ;  год в CX (от 1980 до 2099)
              mov     month,dh     ; месяц в DH (от 1 до 12)
              mov     day,dl       ; день в DL (от 1 до 31)

              В приведенной  программе день недели возвращается в AL.  Как
         видно из программы,  для вызова MS-DOS или  POM-BIOS  выполняются
         следующие шаги:
              - Занесение в регистры соответствующих кодов команд и  пара-
         метров.
              - Осуществление  прерывания,   соответствующего   вызываемой
         функции.
              - Возврат,  считывание возвращаемых параметров и статуса ин-
         формации из регистров.
              Для считывания времени из MS-DOS используется прерывание int

                                      - 5-8 -
         21h с кодом команды AH=2ch, как показано в следующей программе:

              mov     ah,2ch       ; cчитывание времени
              int     21h          ; вызов DOS
              mov     hours,ch     ; часы в CH (от 0 до 23)
              mov     mins,cl      ; минуты в CL (от 0 до 59)
              mov     secs,dh      ; секунды в DH (от 0 до 59)
              mov     msec,dl      ; 10 мсек в DL (от 0 до 99)

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

                   Передача данных в операционной системе MS-DOS

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

                         ЪДДДДДДД¬
                         ¦ Порты ¦
                         АДДДВДДДЩ
                             ¦
                ЪДДДДДДДДДДДДБДДДДДДДДДДДДД¬
                ¦                          ¦
          ЪДДДДДБДДДДДДДДДД¬      ЪДДДДДДДДБДДДДДДДДД¬
          ¦   Порты цент-  ¦      ¦ Порты операцион- ¦
          ¦   рального про-¦      ¦ ной системы DOS  ¦
          ¦   цессора CPU  ¦      ¦                  ¦
          АДДДДДДДДДДДДДДДДЩ      АДДДДДДДДВДДДДДДДДДЩ
           Доступ к ним ве-                ¦
           дется по коман-                 ¦
           дам IN и OUT           ЪДДДДДДДДБДДДДДДДДД¬
                                  ¦                  ¦
                                  ¦                  ¦
                          ЪДДДДДДДБДДДДДД¬    ЪДДДДДДБДДДДДДД¬
                          ¦Последователь-¦    ¦ Параллельные ¦
                          ¦ ные порты    ¦    ¦ порты        ¦
                          АДДДДДДДДДДДДДДЩ    АДДДДДДДДДДДДДДЩ
                          Доступ к ним ве-    Доступ к ним ве-
                          дется по обраще-    дется по обраще-
                          ниям к функциям     ниям к функциям
                          операционной сис-   операционной сис-
                          темы DOS и системы  темы DOS и системы
                          ПЗУ-BIOS            ПЗУ-BIOS

                      Рис. 5-6. Типы портов и методы доступа


                                      - 5-9 -

              "Порты" используются  операционной системой MS-DOS для пере-
         дачи данных.  Данные,  считываемые через порт, представляют собой
         данные внешней среды.  Данные,  поступающие в порт (иначе - "дан-
         ные,  записываемые в порт"), влияют (управляют) на внешнюю среду.
         В  операционной системе MS-DOS существует два типа портов:  порты
         центрального процессора CPU и порты операционной  системы MS-DOS.
         На рисунке 5-6 представлены два типа портов и методы,  используе-
         мые для доступа к этим портам.  Физически эти порты могут  выгля-
         деть  одинаково.  И  только по методу доступа их можно различить.
         Порты центрального процессора CPU называются  также  "логическими
         портами".
              Порты центрального процессора CPU представляют передачу дан-
         ных низкого уровня. Обращение к ним происходит непосредственно от
         процессора. Каждый порт центрального процессора CPU идентифициру-
         ется  своим  адресом.  Может существовать до 65536 разных портов.
         Операционная система MS-DOS использует некоторые порты  централь-
         ного процессора CPU для программируемого таймера и для таких спе-
         цифических целей,  как общение с клавиатурой и  обмен  данными  с
         дисками. Другие порты центрального процессора CPU могут использо-
         ваться пользователем для других целей.  Основной способ обращения
         к этим портам заключается в применении команд IN и OUT языка  Ас-
         семблер. По команде IN происходит считывание одного байта или од-
         ного слова из порта.  По команде WRITE происходит  запись  одного
         байта или одного слова в порт.
              В операционной  системе MS-DOS порт 61h центрального процес-
         сора CPU используется для говорящего устройства.  Второй  младший
         бит  (бит  1) используется для управления звуком,  поступающим от
         говорящего устройства.  Если этот бит установлен в значение  "1",
         говорящее устройство включено.  В противном случае оно выключено.
         Другие биты этого порта управляют другими функциями. Ниже листинг
         5-1 содержит программу,  использующую  порт говорящего устройства
         для генерации звуковых сигналов:

             Листинг 5-1. Генерация звуковых сигналов при помощи порта
                               говорящего устройства
         ----------------------------------------------------------------

                      in     al,61h    ; 61h - это адрес порта говоря-
                                       ;       щего устройства
                      mov    bl,Ofch   ; маска для сброса битов О и 1
                      and    al,bl     ; маскировка al
            noise_on:
                      or     al,2      ; бит 1 в al установлен в зна-
                                       ; чении "1"
                      out    61h,al    ; включение говорящего уст-
                                       ; ройства
                      mov    cx,Offh   ; время включения
            time_1:
                      loop   time_1
            noise_off:
                      and    al,bl     ; сброс битов 0 и 1
                      out    61h,al    ; выключение говорящего уст-
                                       ; ройства
                      mov    cx,Offh   ; меньшее значение в cx озна-
                                       ; чает повышенную частоту
                                       ; звука

                                      - 5-10 -
            time_2:
                     loop    time_2
                     push    ax        ; сохранение ax
                     mov     ah,1      ; считать значение состояния
                                       ; клавиатуры
                     int     16h       ; вызов средств клавиатуры
                                       ; BIOS
                     pop     ax        ; сохранить ax до выполнения
                                       ; перехода

                     jnz     exit      ; символ был набран
                     jmp     noise_on  ; символ не был набран
            exit:
                     ret
         ----------------------------------------------------------------

            В представленной выше программе говорящее  устройство повторно
         включается  и  выключается  путем записи байта в порт 61h.  После
         включения говорящего устройства команда LOOP выполняется  с целью
         предоставления  этому  устройству некоторого времени  для  работы
         прежде чем оно будет отключено.  Звук продолжает поступать до тех
         пор, пока с клавиатуры не будет введен какой-нибудь символ. Функ-
         ция прерывания "int 16h"  используется  для  считывания  значения
         состояния клавиатуры.
            В таблице 5-3 представлены номера портов центрального  процес-
         сора CPU, используемые в операционной системе MS-DOS для каких-то
         конкретных целей.

                                                         Таблица 5-3
                Порты центрального процессора CPU, используемые для
                                определенных целей
         ДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДД
            Порт центрального   ¦ Адрес ввода/вывода ¦Вектор прерыва-
            процессора CPU      ¦                    ¦      ния
         ДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДД
            Таймер              ¦ 040=043            ¦     8
            Вторичный RS-232    ¦ 2F8=2FF            ¦    11
            Клавиатура          ¦ 060=063            ¦     9
            Жесткий диск        ¦ 320=32F            ¦    13
            Печатающее уст-     ¦ 378=37F            ¦    15
            ройство(принтер)    ¦                    ¦
            Монохромный дисп-   ¦ 380=3BF            ¦     -
            лей                 ¦                    ¦
            Цветной дисплей     ¦ 3D0=3DF            ¦     -
            Гибкий диск         ¦ 3F0=3F7            ¦    14
            Первичный RS-232    ¦ 3F8=3FF            ¦    12
         ДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДД

              Порты операционной системы MS-DOS предназначены для передачи
         данных  высокого уровня.  Это значит,  что процессор не управляет
         непосредственно этими данными, а доступ к ним осуществляется пос-
         редством  обращения к функциям операционной системы MS-DOS и сис-
         темы  ПЗУ-BIOS.  Существует  два типа портов операционной системы
         MS-DOS, известные  под названием "последовательных портов" и "па-
         раллельных портов".
              Параллельные порты  в основном разрабатываются для подключе-

                                      - 5-11 -
         ния к ним печатающих устройств (принтеров). Данные через эти пор-
         ты проходят параллельно,  то есть, все восемь битов, составляющие
         байт,  передаются на принтер одновременно.  Операционная  система
         DOS  поддерживает  передачу потока данных через параллельный порт
         только в одном направлении (то есть от  компьютера  к  принтеру),
         хотя  сами по себе аппаратные средства могут осуществлять функции
         и ввода и вывода. Ниже предложена часть программы, в которой про-
         исходит вывод строки,  заданной при помощи DS:DX,  в параллельный
         порт.  Функция прерывания операционной системы MS-DOS "int 21h" с
         параметром  AH=40H  (записать  файл)  используется для выполнения
         этого условия.  Регистр BX содержит описатель файла для стандарт-
         ного  устройства  печати (4).  После обработки запроса регистр ax
         будет содержать количество символов,  действительно помещенных  в
         параллельный порт.

                        mov    ah,40h            ; функция = запись
                                                 ;            файла
                        mov    bx,4              ; описано для стан-
                                                 ; дартного принтера

                        mov    cx,20             ; подсчет числа сим-
                                                 ; волов

                        mov    dx,seg OUTSTR     ;
                        mov    ds,dx             ;
                        mov    dx,offset OUTSTR  ;  ds:dx указывает на
                                                 ;  OUTSTR
                        int    21h               ;  обращение к опера-
                                                 ;  ционной системе
                                                 ;  MS-DOS
                        jc     failed            ;  установленное в
                                                 ;  единицу  значение
                                                 ;  переноса означает
                                                 ;  сбой принтера

              Последовательные порты  обычно  используются для подключения
         модемов,  а также для подключения устройства "мышь". Данные через
         последовательные порты передаются по одному биту по одному и тому
         же проводнику.  Операционная система  DOS  поддерживает  передачу
         данных в двух направлениях, то есть, в компьютер и от компьютера.
              Передача данных через последовательные порты ведется  асинх-
         ронно.  Поэтому  на обоих концах линии передачи должен быть уста-
         новлен ряд согласующих параметров.   Скорость передачи  данных  в
         бодах "последовательного порта" равна числу битов, передаваемых в
         секунду.  Скорость передачи данных в бодах, поддерживаемая опера-
         ционной  системой MS-DOS версии 3.3,  лежит в диапазоне от 110 до
         19200 битов в секунду. "Длина слова" представляется числом битов,
         составляющих символ.  Это значение может равняться 7 или 8. "Чет-
         ность" - это простой механизм обнаружения ошибок в линии  переда-
         чи. Согласно стандартам, принятым в интерфейсах RS-232 для после-
         довательной передачи данных, значением параметра  четности  может
         быть "четно" или " нечетно" (два способа проверки ошибок) или мо-
         жет не существовать проверки на четность. Каждый символ отделяет-
         ся от других битами,  называемыми "битами останова".  Можно зада-
         вать один или  два  бита  останова.  Эти  параметры  должны  быть
         проинициализированы до начала процесса передачи данных.
              Функция прерывания "int 14h" системы BIOS  используется  для

                                     - 5-12 -
         передачи данных через последовательный порт. Инициализация после-
         довательного порта выполняется  установкой  в  регистре  AH  кода
         функции 0. Параметры передачи задаются в регистре AL, как это по-
         казано в таблице 5-4.  "Номер порта" указывается в DX. Существует
         четыре  последовательных портов в операционной системе MS-DOS. Их
         именами являются значения от COM1 до COM4.  В представленной ниже
         программе  последовательный порт инициализируется исходно для пе-
         редачи данных со скоростью 9600 бод,  слов  длиной в 8  битов,  с
         одним битом  останова при отсутствии проверки на четность.  После
         выполнения запроса к функции регистр AH содержит значение состоя-
         ние порта.

                   mov     ah,0      ; инициализировать последователь-
                                     ; ный порт
                   mov     al,0e3h   ; 9600 бод, 8-битовое слово, от-
                                     ; сутствие проверки на четность,
                                     ; 1 бит останова
                   mov     dx,0      ; порт COM1 инициализируется
                   int     14h       ; вызов системы ПЗУ-BIOS


                                                         Таблица 5-4
                     Параметры связи, задаваемые в регистре AL
         ДДДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДД
          Скорость    ¦   Четность   ¦ Биты останова  ¦ Длина слова
          в бодах     ¦              ¦                ¦
         ДДДДДДДДДДДДД¦ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДД
         Биты   Ско-  ¦ Биты Чет-    ¦        Биты    ¦ Биты  Длина
         7,6,5  рость ¦ 4,3  ность   ¦ Бит 2  останова¦  1,0
         ДДДДДДДДДДДДД†ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДД
         000    110   ¦x0  Отсутствует  0     1 бит   ¦  10   7 битов
         001    150   ¦01  Нечетная  ¦  1     2 бита  ¦  11   8 битов
         010    300   ¦11  Четная    ¦                ¦
         011    600   ¦              ¦                ¦
         100   1200   ¦              ¦                ¦
         101   2400   ¦              ¦                ¦
         110   4800   ¦              ¦                ¦
         111   9600   ¦              ¦                ¦
         ДДДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДД

              В предлагаемом  ниже куске программы символ 'x' записывается
         в последовательный порт. При возврате, если бит 7 регистра AH ус-
         тановлен в значение "0",  обращение к функции было успешным. Зна-
         чение "0" *, в бите 7 регистра AH указывает на сбой.
         --------------------------
             * Возможно,  в оригинал текста вкралась опечатка:  или  здесь
               или  в предыдущем предложении должно стоять значение "1"...
               (Примеч. переводчика.)

                   mov     ah,1      ; функция 1= запись символа
                   mov     al,'x'    ; регистр AL содержит символ
                   mov     dx,0      ; запись в порт COM1
                   int     14h       ; обращение к системе ПЗУ-BIOS

              Время, требуемое для передачи данных через порты, обычно за-
         висит от характеристик внешних устройств.  Например, время, необ-
         ходимое для записи/считывания 1 байта на жесткий диск или с жест-

                                      - 5-13 -
         кого диска,  зависит от нескольких факторов: типа жесткого диска,
         таких  параметров жесткого диска,  как количество головок и коли-
         чество цилиндров,  размера порций ввода/вывода на  жесткий  диск,
         структуры файлов, количества файлов и так далее. Если ваша систе-
         ма реального времени использует внешние устройства,  Вам  следует
         провести несколько опытов,  чтобы  снять  время, затрачиваемое на
         выполнение операций. Поскольку разрешающая способность часов сис-
         темы  в  операционной  системе MS-DOS равна 10 м/сек,  операция в
         опыте  должна повторяться много раз с целью получения точных вре-
         менных оценок. Они изображены ниже на рисунке 5-7:

              ЪДДДДДДДДДДДДДДДДДДД¬
              ¦                   ¦
              ¦  Передача данных  ¦
              ¦                   ¦
              АДДДДДДДДДВДДДДДДДДДЩ
                        ¦
             ЪДДДДДДДДДД†ДДДДДДДДДДДД¬
             ¦          ¦            ¦
          ЪДДБДДДД¬ ЪДДДБДДДДД¬ ЪДДДДБДДД¬
          ¦Метод  ¦ ¦ Прямой  ¦ ¦Метод   ¦
          ¦упоря- ¦ ¦ доступ  ¦ ¦доступа ¦
          ¦дочен- ¦ ¦ к памяти¦ ¦по пре- ¦
          ¦ного   ¦ ¦ (DMA)   ¦ ¦рываниям¦
          ¦опроса ¦ ¦         ¦ ¦        ¦
          АДДДДДДДЩ АДДДДДДДДДЩ АДДДВДДДДЩ
                                    ¦
                    ЪДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДД¬
                    ¦               ¦              ¦
                    ¦               ¦              ¦
             ЪДДДДДДБДДДДДД¬    ЪДДДБДДД¬     ЪДДДДБДДДД¬
             ¦ Устройства  ¦    ¦ Таймер¦     ¦Программа¦
             ¦ ввода/выво- ¦    ¦       ¦     ¦         ¦
             ¦ да          ¦    АДДДДДДДЩ     АДДДДДДДДДЩ
             АДДДДДДДДДДДДДЩ

                         Рис. 5-7. Методы передачи данных

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

            read:     in   ax,dx      ; считывание данных - 12 циклов
                      add  di,2       ; следующее место назначения -
                                        4 цикла
                      mov  [di],ax    ; сохранение данных - 18 циклов
                      loop read       ; выполнение цикла до полного

                                      - 5-14 -
                                      ; завершения - 17 циклов
                                      ; общее количество -51 цикл
                                            для 8088
                                      ; общее количество -43 цик-
                                            ла для 8086
              Для микропроцессора  модели  8088,  работающего  с  частотой
         4,77 МГц 51 цикл занимает 10,69 микросекунд,  что приводит к ско-
         рости передачи данных на частоте 93 КГц.  Эта  скорость  передачи
         данных  означает,  что  компьютер может считывать данные максимум
         93000 раз из порта.
              В приводимом ниже куске программы происходит проверка на на-
         личие готовых к пересылке данных в  последовательном  порту.  Для
         этого используется функция прерывания "int 14h" со значением AH=3
         (требование к состоянию последовательного  порта).  При  возврате,
         если бит 0 регистра AH имеет значение "1", это значит, что имеются
         готовые данные.  Программа обычно ожидает появление готовых данных
         и они возвращаются в регистр AL.

            wait:
                     mov      ah,3       ; считать значение состояния
                                         ; последовательного порта
                     mov      dx,0       ; требуется состояние порта
                                         ; COM1
                     int      14h        ; обращение к ПЗУ-DIOS
                     and      ah,1       ; данные готовы?
                     jz       wait       ; нет, ожидать, пока данные
                                         ; не будут готовы
                     mov      ah,2       ; Функция=считывания данных
                     mov      dx,0       ; считывание из порта COM1
                     int      14h        ; обращение к ПЗУ-BIOS


              Передача данных методом прямого доступа к памяти (DMA)

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

                     Передача данных методом прерываний данных

              Передача данных методом прерываний данных используется, ког-
         да передача данных ведется асинхронно (то есть, время, отведенное
         для  передачи  данных заранее не определяется).  И такая передача
         данных обычно происходит сравнительно не часто. Прерывание указы-
         вает на появление какого-либо внешнего события в процессоре, тре-
         бование может вызываться каким-либо внешним по отношению  к  про-
         цессору устройством, часами (таймером) или какой-либо программой.
         При наступлении прерывания процессор  приостанавливает выполнение
         текущей  программы ("подвешивает" ее),  считывает адрес сервисной
         программы прерываний (программы ISR) из таблицы векторов прерыва-

                                      - 5-15 -
         ний и выполняет последнюю.
            Основными функциями,  выполняемыми  сервисной  программой ISR,
         являются :
            1. Разрешение прерываний таким образом, что могут обслуживать-
         ся прерывания более высоких приоритетов.
            2. Сохранение регистров, которые будут использоваться програм-
         мой ISR.
            3. Выполнение функций обработки,  связанных с прерыванием, как
         можно быстрее.
            4. Восстановление содержимого сохраненных регистров.
            5. Выполнение  команды IRET для возобновления выполнения  пре-
         рванной программы.

            Представленная ниже  программа  содержит  структуру  программы
         ISR:
                  sti                       ; разрешить прерывания
                  push      ax              ; сохранить только те
                  push      bx              ; регистры, которые ис-
                  push      cx              ; пользуются программой
                  push      dx              ; ISR
                  .
                  .
                  .
                  mov       ax,cs           ; локальные данные, к ко-
                                            ; торым будет вестись об-
                                            ; ращение
                  mov       ds,ax           ; с использованием области
                                            ; DS
                  .
                  .
                  .                         ; обработать прерывания

                  pop       dx              ; восстановить регистры в
                  pop       cx              ; обратном порядке
                  pop       bx              ;
                  pop       ax              ;
                  iret                      ; возобновить выполнение
                                            ; прерванной программы

              При появлении прерывания процессор сохраняет три  слова (CS,
         IP, флаги) и считывает два слова (адрес программы ISR). Таким об-
         разом, на обслуживание любого прерывания в процессоре 8088 затра-
         чивается 71 цикл, а в процессоре 8086 - 51 цикл. Не требуется до-
         полнительных  затрат на задание прерывания и  его  сброс,  потому
         что процесс обслуживания прерывания уже сбрасывает его (это назы-
         вается "автоматическое завершение режима прерывания").

                         Сравнение методов передачи данных

              Сравнение времен и максимальных скоростей передачи данных не
         методами прямого доступа (DMA) представлено в таблице  5-5.  Вре-
         менные затраты (циклы и времена) в таблице вычисляются только для
         тех циклов,  в которых не происходит действительной передачи дан-
         ных.  Информация о временных затратах дается только в целях  при-
         близительного сравнения.  Например, временные затраты на выполне-
         ние "программного цикла" получают при выполнении следующей коман-
         ды языка ассемблер:

                     again:  loop again

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

            again:
                   in     ax,dx        ; порт задается в DX
                   test   ax,bx        ; сравнение регистр/регистр
                   jnz    again        ; повторять цикл до готовности


                                                         Таблица 5-5
                 Скорости передачи данных для не -DMA интерфейсов
         ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДД¬
         ¦Готовые данные,¦   Затраты     ¦   Передача   ¦Макси-   ¦
         ¦определяемые:  ¦   времени     ¦    данных    ¦мальная  ¦
         ¦               ГДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДґскорость ¦
         ¦               ¦ циклы  время  ¦  циклы  время¦передачи ¦
         ¦               ¦               ¦              ¦данных   ¦
         ГДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДД†ДДДДДДДДДґ
         ¦Программный    ¦17  3.564 мксек¦  -      -    ¦ -       ¦
         ¦цикл           ¦               ¦              ¦         ¦
         ¦(8088,4.77 MГц)¦               ¦              ¦         ¦
         ¦               ¦               ¦              ¦         ¦
         ¦Упорядоченный  ¦27  5.660 мксек¦51 10.692мксек¦61 КГц   ¦
         ¦опрос          ¦               ¦              ¦         ¦
         ¦(8088;4,77 МГц)¦               ¦              ¦         ¦
         ¦               ¦               ¦              ¦         ¦
         ¦Прерывание     ¦115 24.109мксек¦43  9.015мксек¦30 КГц   ¦
         ¦(8088;4,77 МГц)¦               ¦              ¦         ¦
         ¦               ¦               ¦              ¦         ¦
         ¦Прерывание     ¦ 83 10.375мксек¦43  5.375мксек¦63 КГц   ¦
         ¦(8086; 8 МГц)  ¦               ¦              ¦         ¦
         АДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДЩ

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

                        Средства ускоренной записи программ

               Многие программные средства и блоки сбора данных (стандарт-
         ные  и  обычные)  могут  использоваться  для ускорения выполнения
         программ oперационной системы MS-DOS.  Эти средства следует  рас-
         сматривать только,  если структура системы реального времени тре-
         бует  ускоренного ответа от компьютера.  И хоть исчерпывающее об-
         суждение всего этого выходит за рамки данной  главы,  мы  все  же
         рассмотрим некоторые из этих средств в этом разделе.
              Если прикладная программа требует вычислений с использовани-

                                      - 5-17 -
         ем математических формул с плавающей запятой,  применение матема-
         тического  процессора  8087 (или 80287/80387) может улучшить ско-
         рость  вычислений  на  несколько  порядков.   Фирма   "Интел"   -
         производитель  микропроцессоров  8087  -  выпускает оборудование,
         скорость которого в некоторых математических операциях  в  тысячу
         раз  возрастает,  когда  микропроцессор  8087 используется вместо
         стандартных программ  математического  программного  обеспечения.
         Микропроцессор  8087  предоставляет  системе команды для быстрого
         выполнения операций с плавающей запятой.  К таким операциям отно-
         сятся следующие действия:  преобразование чисел, основные матема-
         тические действия и некоторые трансцендентные функции  (например,
         нахождение синусов и косинусов и логарифмов).  Поскольку же мате-
         матические программы стандартно  присутствуют  в  микропроцессоре
         8087,  а не в памяти программ, использование микропроцессора 8087
         тоже может привести к уменьшению размеров программ.  Подробнее  о
         программировании  можно узнать из главы 10 "Программирование рас-
         ширения числовой обработки фирмы "Интел"".
              Если требуется просмотреть какой-либо массив,  можно исполь-
         зовать команду XLAT во всех  процессорах,  кроме  микропроцессора
         8088. Команда XLAT имеет возможность производить быстрое индекси-
         рование в 256-байтовой таблице и получать  содержимое  указанного
         адреса,  что показано на рисунке 5-8.  Объединяя несколько команд
         XLAT в цепочки, можно управлять просмотром больших таблиц.
              Вместо исполнения команды IN/OUT в цикле для передачи набора
         байтов можно  исполнять  команду  INS/OUTS  для  микропроцессоров
         80188, 80186, 80286 и 80386.
              Используя средства DMA (средства метода  прямого  доступа  к
         памяти)  для  передачи данных можно значительно увеличить быстро-
         действие системы.  Передача данных происходит с полной скоростью,
         на  которую способна шина и память,  потому что в это действие не
         вовлекаются ресурсы процессора. Другое преимущество в данном слу-
         чае заключается в том,  что передача данных не ограничивается ши-
         риной шины данных, ведущей к процессору.

                                                        Регистр BX
                                                         указывает
                                                         на базу
                        Перед выполнением ЪДДДДДДДДД¬ ДДДДДДДДДДДД
                        команды XLAT,     ¦         ¦  
            Регистр AX  AL указывает на   ¦         ¦  ¦
          ЪДДДДДДВДДДДД¬  ЭЛЕМЕНТ         ГДДДДДДДДДґ  ¦
          ¦      ¦ AL  ГДДДДДДДДДДДДДДДДД¦ ЭЛЕМЕНТ ¦  Максимальное
          ¦      ¦     ¦       ЪДДДДДДДДДДґ         ¦  количество
          АДДДДДДБДДДДДЩ       ¦          ГДДДДДДДДДґ  ¦ элементов
                              ¦          ¦         ¦  ¦ равно 256
                    ¦          ¦          АДДДДДДДДДЩ  
                    АДДДДДДДДДДЩ
                   После выполнения
                   команды XLAT,
                   ЭЛЕМЕНТ замещает AL

                Рис. 5-8. Работа команды XLAT при просмотре таблиц

              Использование макрокоманд  вместо  обращения  к  стандартной
         подпрограмме ускоряет работу программы. Это происходит вследствие
         того,  что макрокоманда производит расширение на месте, а при об-
         ращении  к  стандартной  подпрограмме  приходится  помещать адрес

                                      - 5-18 -
         возврата в стек,  обычно сохранять регистры и потом делать  пере-
         ход.  Обратитесь к Главе 1 "Структурированное программирование 1:
         Инструменты для структурированного программирования ",  где более
         подробно говорится о задании макрокоманд.

              Случаи, когда следует использовать операционную команду
                 MS-DOS для прикладных программ реального времени

              Решение об использовании  операционной  системы  MS-DOS  для
         конкретной  прикладной  программы зависит от нескольких соображе-
         ний. Следует учитывать:
            . время ответа
            . интервалы
            . количество вводов
            . количество выводов
            . используемый тип процессора
            . частоту системных часов
            . структуру системы

              Из всех  перечисленных  выше  соображений  только  структура
         прикладной программы реального времени зависит  от  разработчика:
         может быть выбрана более быстрая операционная система MS-DOS. Все
         другие соображения  продиктованы внешней средой и не  могут  быть
         изменены разработчиком.
              Нам нужно вычислить время ответа для прикладной программы, а
         также нужно вычислить время ответа, которое может обеспечить дан-
         ная операционная система  MS-DOS  в  среде  прикладных  программ.
         Обычно  бывает  несложно  определить  требуемое  время ответа для
         прикладной программы.  Рассмотрим простой  пример  сбора  данных.
         Предположим,  что за одну секунду Вы должны собрать 50 000 байтов
         данных  и  обработать  их.   Это значит,  что вы имеете 1/50000 =
         20 микросекунд для сбора и обработки каждого байта информации.
              В другом примере предположим  Вам  требуется  время  ответа,
         равное 10 мсек,  для контроля и принятия корректирующего действия
         для управления температурным режимом в химическом  процессе. (Эти
         действия  могут  основываться  на таких временных характеристиках
         оборудования,  которые управляют температурами, а также самим ха-
         рактером протекающего процесса). Таким образом, время ответа, тре-
         буемое прикладной программе,  присуще как этой прикладной програм-
         ме, так и существующей среде.
              Гораздо труднее определить время ответа, которое может обес-
         печить система реального времени. Для определения времени ответа,
         которое может поддерживаться операционной системой MS-DOS,   рас-
         смотрим сначала существующую операционную систему MS-DOS и имеющ-
         ееся оборудование (если оно действительно присутствует),  а также
         некоторый  простой  способ  разработки  программного  обеспечения
         (разные методы разработки будут обсуждаться позднее).  Теперь да-
         вайте вычислим время ответа, которое равно времени, затрачиваемо-
         му  на  необходимый ввод,  отработку и вывод,  и всех связанных с
         этими действиями задержек. Если это время ответа меньше требуемо-
         го времени ответа,  это значит, что Вы нашли приемлемую структуру
         системы, используя операционную систему MS-DOS.
              И снова рассмотрим пример со сбором  данных,  где  требуемым
         временем  ответа является значение в 20 микросекунд. Операционная
         система MS-DOS может обеспечить время ответа  в  20  микросекунд,
         если  будет иметься достаточное количество основной первичной па-
         мяти для размещения требуемого объема данных. Но если такого объ-

                                      - 5-19 -
         ема памяти нет,  возможно,  Вам придется размещать данные во вто-
         ричной памяти. В этом случае двадцати микросекунд может оказаться
         недостаточно для размещения данных.
              Если время   ответа,  поддерживаемое  операционной  системой
         MS-DOS,  не меньше,  чем требуемое время ответа, Вам следует про-
         вести  ряд опытов с другими техническими средствами в поисках бо-
         лее быстрых программ (они уже рассматривались  выше), стандартных
         или  обычных блоков,  более быстрого процессора,  других структур
         системы и использовать более быстрое оборудование(может быть, это
         будет более дорогостоящее устройство). Этот выбор является наибо-
         лее трудной частью проектирования систем реального времени, а об-
         суждение  всех этих средств далеко выходит за пределы этой главы.
              Требуемая продолжительность временного  интервала определяет
         частоту обработки транзакций. В зависимости от требуемой обработ-
         ки оперативной системы MS-DOS может управлять определенным макси-
         мальным числом транзакций в секунду.  Если общее количество тран-
         закций может  больше,  чем  то  число,  которым  может  управлять
         операционной системой MS-DOS, очевидно, что систему MS-DOS нельзя
         будет использовать.  Например,  если  система  реального  времени
         должна получить 1 миллион транзакций в секунду, операционная сис-
         тема MS-DOS не может использоваться для такой системы.
              Возможность применения операционной системы  MS-DOS  зависит
         также от  необходимости  использования  мультизадачности. В общем
         случае операционная система MS-DOS не может использоваться , если
         требуется применение мультизадачного режима.
              Например, операционная система MS-DOS не может быть примене-
         на в банковской системе с восемью терминалами.  Однако, оператив-
         ная система MS-DOS,   предназначенная для использования в  персо-
         нальном компьютере IBM PC/AT,  предусматривает наличие нескольких
         свойств, позволяющих организовать  очень  простой  мультизадачный
         режим. Это будет обсуждено позже. Также мы будем позднее рассмат-
         ривать концепцию "циклических планировщиков"  в  качестве  метода
         проектирования очень простой формы мультизадачности.
              В общем, мы можем сказать, операционная система MS-DOS может
         быть  использована для большинства однонаправленных систем,   не-
         скольких двунаправленных устойчивых систем и очень  малого  коли-
         чества двунаправленных потенциально неустойчивых систем.  Сущест-
         вует  несколько  сравнительно  простых,  но  часто   используемых
         прикладных  программ реального времени,  в которых может быть ис-
         пользована операционная система MS-DOS. В эти прикладные програм-
         мы включаются системы управления домашним хозяйством, системы ла-
         бораторных измерений, упрощенные системы роботов и т.д.

             Проектирование систем реального времени с использованием
                            операционной системы MS-DOS

              Проектирование систем  реального  времени  с  использованием
         операционной системы MS-DOS  затруднено  отсутствием  официальной
         методологии  такого  проектирования.  Эта нехватка общего подхода
         выражается в возникновении таких проблем, как:
            1. В работающих системах реального времени могут возникать не-
         объяснимые аварийные ситуации или результаты работы будут  выгля-
         деть странно из-за проблем синхронизации.
            2. Существующие реализации указанных систем  становятся  неуп-
         равляемыми  вследствие произошедших в них изменений и расширений.
            3. Когда процесс программирования системы закончен, нельзя по-
         ручится за ее работоспособность в дальнейшем.

                                      - 5-20 -

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

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

                ЪДДДДДДДДДДДДДДДДДДД¬
                ¦     Методы        ¦
                ¦  проектирования   ¦
                ¦                   ¦
                АДДДДДДДДДВДДДДДДДДДЩ
               ЪДДДДДДДДДДБДДДДДДДДДДДД¬
               ¦                       ¦
         ЪДДДДДБДДДДД¬            ЪДДДДБДДД¬
         ¦ синхронные¦            ¦мульти- ¦
         ¦(однознач- ¦            ¦задачный¦
         ¦ ные)      ¦            ¦режим   ¦
         АДДДДДДДДДДДЩ            АДДДВДДДДЩ
                        ЪДДДДДДДДДДДДД†ДДДДДДДДДДДД¬
                        ¦             ¦            ¦
                 ЪДДДДДДБДДДДДД¬  ЪДДДБДДД¬   ЪДДДДБДДДДД¬
                 ¦упорядоченный¦  ¦главный¦   ¦цикличес- ¦
                 ¦опрос (без   ¦  ¦цикл с ¦   ¦кое плани-¦
                 ¦прерываний)  ¦  ¦прерыва¦   ¦рование   ¦
                 АДДДДДДДДДДДДДЩ  ¦ниями  ¦   АДДДДДДДДДДЩ
                                  АДДДДДДДЩ

             Рис. 5-9. Методы проектирования систем реального времени

             Пример упрощенной системы управления домашним хозяйством

              Давайте рассмотрим упрощенную  систему  управления  домашним
         хозяйством, предназначенную следить за расходом тепла, предупреж-
         дать о пожаре и управлять орошением газонов,  что показано на ри-
         сунке 5-10.  Температура регистрируется температурно-чувствитель-
         ным   устройством.    Некоторый    преобразователь    преобразует
         температуру из аналогового в цифровое значение.  Это значение мо-
         жет быть считано из порта и сравнено с  образцовой  температурой.
         Значение  образцовой  температуры может задаваться пользователем.
         Для простоты давайте будем считать это значение постоянной  вели-
         чиной с именем ref_temp.  Программа adjust_temp,  предназначенная
         управлять температурами,  задана в листинге 5-2. Отметим, что эта
         программа  содержит только важные части программы.  Менее сущест-
         венные подробности (такие,  как объявление и инициализация  пере-
         менных) здесь не показаны.


                                      - 5-21 -
                                    образцовая
                                    температура
                      преобразова-                 преобразова-
                      тели                         тели
          от датчика  ЪДДДДДДДД¬   ЪДДДДДДДДД¬   ЪДДДДДДДДД¬   к наг-
          температур  ¦        ¦   ¦         ¦   ¦         ¦   рева-
          ДДДДДДДДДДД¦        ¦ДД¦         ¦ДД¦         ¦ДДтелю
                      АДДДДДДДДЩ   ¦         ¦   АДДДДДДДДДЩ
          от датчика  ЪДДДДДДДД¬   ¦компьютер¦   ЪДДДДДДДДД¬   к сигна-
          огня(дыма)  ¦        ¦   ¦         ¦   ¦         ¦   лу тре-
          ДДДДДДДДДДД¦        ¦ДД¦         ¦ДД¦         ¦ДДвоги
                      АДДДДДДДДЩ   ¦         ¦   АДДДДДДДДДЩ
          от таймера реального     ¦         ¦   ЪДДДДДДДДД¬   к венти-
          времени                  ¦         ¦   ¦         ¦   лю оро-
          ДДДДДДДДДДДДДДДДДДДДДДДД¦         ¦ДД¦         ¦ДДшения
                                   ¦         ¦   АДДДДДДДДДЩ   газонов
                                   АДДДДДДДДДЩ

                Рис. 5-10.  Упрощенная система управления домашним
                                    хозяйством

                        Листинг 5-2. Программа adjust_temp
         ----------------------------------------------------------------
            adjust_temp:
                    ; delta- для избежания колебаний температур
                    ; ref_temp- значение образцовой температуры
                    ; temp_port- порт, через который выводится
                    ;            информация  по  управлению
                                 температурой
                    ; inc_cod-  сегмент программы для повышения
                                температуры
                    ; dec_code- сегмент программы для снижения
                                температуры
                    ; read_port- порт, через который ведется
                    ;            считывание значений текущей
                                 температуры
                    in   al,read_port ; считать температуру в AL
                    mov  bl,ref_temp  ; получить ref_temp в BL
                    mov  cl,bl  ; BL будет использоваться позднее
                    sub  cl,delta     ; ref_temp - delta в CL
                    cmp  al,cl        ; текущая температура меньше
                                      ; ref_temp - delta ?
                    jl   increase     ; тогда увеличить температуру
                    add  bl,delta     ; ref_temp + delta в BL
                    cmp  al,bl        ; текущая температура больше
                                      ; ref_temp + delta ?
                    jg   decrease     ; тогда уменьшить температуру
                    ret               ; сделано
            decrease:
                    mov  al,dec_code  ; dec_code
                    out  temp_port,al ; temp_port
                    ret
            increase
                    mov  al,inc_code  ;inc_cod подлежит выводу в
                    out  temp_port,al ; temp_port
                    ret
         ----------------------------------------------------------------

                                      - 5-22 -

              Отметим, что нагревание не меняется, когда температура оста-
         ется  в пределах диапазона от ( ref_temp - delta) до (ref_ temp +
         delta),  как это показано на рисунке 5-11.  Здесь значение  delta
         представляет собой маленькую величину допуска (например,  в одном
         градусе 1F * и он используется для избежания температурных  коле-
         баний.
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            * Один градус по Фаренгейту (1F) приблизительно равен по Цель-
              сию 0,56 градуса (0,56 C) (Примеч. переводчика.)


                              Образцовая температура
                                    (ref_temp)

                                        ¦
             низкая темпера-            ¦            высокая темпера-
                тура         ¦ДdeltaДД¦ДДdeltaД¦ тура
            ДДДДДДДДДДДДДДДДД†ДДДДДДДДДД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДД
                   повышение       температура       уменьшение
                  температуры      не меняется       температуры

             Рис. 5-11. Использование допуска delta небольшой величины
                       для избежания температурных колебаний

              Для того,  чтобы понять концепцию колебаний,  будем считать,
         что значение delta равно нулю.  В этом случае температуру следует
         корректировать  всякий  раз,  когда  она  точно не равна значению
         ref_temp. Предположим,  что  температура оказалась немного меньше
         значения ref_temp.  Теперь при обращении к процедуре будет предп-
         ринято действие, повышающее температуру. В результате температура
         повысится до значения, превышающего величину ref_temp. Теперь при
         очередном вызове процедуры будет предпринято действие, понижающее
         температуру. В результате таких действий температура будет то по-
         вышаться, то понижаться попеременно. Вот это и называется колеба-
         нием температур. Таким образом, применение величины delta (малой,
         но  не равной нулю) способствует погашению колебаний,  потому что
         температура не меняется при этом в небольшом  диапазоне ref_temp.
             Теперь давайте  рассмотрим  программу  по активизации сигнала
         пожарной тревоги. Нам следует только послать какой-нибудь сигнал,
         который отключит потом сигнал о пожаре.  Эта процедура под назва-
         нием initiate_alarm представлена в Листинге 5-3.
                      Листинг 5-3. Программа initiate_alarm:
         ---------------------------------------------------------------
         initiate_alarm:
                         ; alarm_port -порт для подачи сигнала тревоги
                         ; activate_signal  -  сигнал для активизации
                                               тревоги
                         mov        al,activate_signal
                         out        alarm_port,al
                         ret
         ---------------------------------------------------------------

              Далее рассмотрим  процедуру  управления  орошением  газонов.
         Предположим,  что нам нужно поливать газоны по вечерам с 18.30 до
         20.30.  Это требование диктует нам необходимость пользоваться ре-
         альным  временем,  то есть часами.  В листинге 5-4 показана такая

                                      - 5-23 -
         процедура, имеющая имя water_lawn.

                        Листинг 5-4. Программа  water_lawn
         ----------------------------------------------------------------
            water_lawn:
                        ; start_hours -  компонент "часы" начального
                        ;                времени
                        ; start_mins  -  компонент "минуты" начального
                        ;                времени
                        ; stop_hours -   компонент "часы" конечного
                        ;                времени
                        ; stop_miins -   компонент "минуты" конечного
                        ;                времени
                        ; water_port -   порт для управления
                        ;                орошением газона
                        ; start_code   - программа  начала
                        ;                орошения
                        ; stop_code -    программа окончания
                        ;                орошения
                        ; watering  -    переменная состояния,
                        ;                говорящая о том, что
                        ;                началось ли орошение
                        ;                или нет

                   mov   ah,2ch         ; функция считывания времени
                   int 21h              ; обращение к DOS при возврате
                                        ; cx содержит часы и минуты
                   mov   bl,watering    ; началось ли орошение?
                   test  bl,1           ;
                   jz    start_or_not     нет, тогда начать орошение
                   mov   dh,stop_hours  ; не пора ли кончить поливку?
                   mov   dl,stop_mins   ; сравнивать со временем ос-
                                          танова
                   cmp   cx,dx          ; пора прекратить орошение?
                   jl    exit       ; нет,пусть орошение продолжается
                   mov   watering,0     ; орошение прекращено
                   mov   al, stop_code  ; вывести код останова
                   out   water_port, al ; через water_port
                   ret                  ; выполнено
            start_or_not:
                   mov   dh,start_hours ; время начала находится в DX
                   mov   dl,start_mins  ;
                   cmp   cx,dx          ; пора ли начать орошение?
                   jl    exit           ; нет еще
                   mov   watering,1     ; да, орошение началось
                   mov   al,start_code  ; вывести код пуска
                   out   water_port,al  ; через water_port
            exit:
                   ret                  ; выполнено
         ----------------------------------------------------------------

              В этой программе предполагается, что время остановки (значе-
         ние "stop_hours:stop_mins") больше времени начала орошения  (зна-
         чения   "start_hours:start_mins).  Глобальная булевая  переменная
         "watering" используется здесь таким образом, что сигнал на запуск
         или останов поливки должен выдаваться только один раз в день.
             Теперь, когда у нас есть индивидуальные  процедуры управления

                                      - 5-24 -
         нагреванием,  пожарной тревогой и орошением газонов, давайте про-
         ведем исследование требований ко всей системе. Температуру следу-
         ет регулировать повторно каждые несколько минут.  Сигнал пожарной
         тревоги должен раздаваться сразу же после обнаружения  огня  (или
         дыма) - здесь недопустима задержка!
              Газон должен орошаться ежедневно в течение двух часов  начи-
         ная  с 18.30.  Мы предположили,  что соответствующие устройства и
         преобразователи для ввода/вывода данных уже связаны с процессором
         через интерфейс. Теперь мы готовы обсудить общую конструкцию сис-
         темы реального времени для упрощенного  управления  домашним  хо-
         зяйством.
              Мы будем рассматривать три синхронных метода: упорядоченного
         опроса (без прерываний),  основного цикла  с  прерываниями  цикли-
         ческих планировщиков. Как мы уже упоминали, синхронные методы тре-
         буют наличия только одного и задачи в системе для их реализации.

                           Система упорядоченного опроса

              Система упорядоченного  опроса состоит из главного цикла,  в
         течение которого все устройства последовательно опрашиваются (или
         вызываются нужные процедуры) по одному.  Программа для упрощенной
         системы домашнего контроля использует эту структуру,  что демонс-
         трируется программой в системе 5-5.
              В этой программе слова  "wait_loop"  обозначают  программный
         цикл, в котором происходит ожидание в течение некоторого времени.
         В общем случае может потребоваться вводить "передышки"  в системы
         опроса.  Для того, чтобы понять необходимость введения таких "пе-
         редышек",   рассмотрим пример управления управляемой  компьютером
         автомашины.  Предположим,  что компьютер решил повернуть машину в
         право и выдал команду: "поворот направо". Колеса начали выполнять
         поворот, но в этот момент, предположим, компьютер продолжает про-
         верять и оценивать входные данные  с  все  большей  скоростью.  И
         компьютер  продолжает  считать,  что машина еще не поворачивает и
         повторно посылает команду поворота вправо.  Прежде чем  осознать,
         что машина уже поворачивает,  компьютер успевает выдать несколько
         команд "поворота вправо", что приведет к заносу машины. Чтобы из-
         бежать подобной ситуации компьютер должен быть так запрограммиро-
         ван,  чтобы он имел возможность делать паузы после выдачи  команд
         для их выполнения. Но в случае управления домашним хозяйством та-
         кие паузы недопустимы,  так как обнаружение пожара требует немед-
         ленных,  а не "отложенных" действий, как уже было упомянуто выше.

                         Листинг 5-5. Программа ref_level
         ----------------------------------------------------------------
                   ; ref_level - опасный уровень дыма
            forever:
                   call    adjust_temp     ;вызов стандартной подпрог-
                                           ;раммы
                   in      al, smoke_port  ;получить значение уровня
                                           ;дыма
                   cmp     al, ref_level   ;проверка наличия опасности
                   jl      no_danger
                   call    initiate_alarm  ;включить сигнал тревоги
            no_danger:
                   call    water_lawn      ;вызов стандартной подпрог-

                                      - 5-25 -
                                           ;раммы
                   call    wait_loop       ;пауза (отсутствие действий)
                   jmp     forever
            wait_loop:
                   mov     cx, 0ffh        ;значение в cx определяет
                                           ;время ожидания
            wait:
                   loop    wait
                   ret
         ----------------------------------------------------------------

              Преимуществами описанного метода являются:
            . простая конструкция системы
            . быстрое время ответа.

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

                           Основной цикл с прерываниями

              Структуру метода основного цикла с прерываниями можно  расс-
         матривать  как  систему  опроса с прерываниями.  Как и в случае с
         чистой системой опроса,  в этом методе используется понятие  "ос-
         новного цикла".  Во время этого цикла может ничего не делаться, а
         может что-то выполняться,  что будет прерываться так  часто,  как
         того  требуют  внешние  устройства или часы (таймер).  Как только
         возникает какое-либо событие,  прерывание посылается в процессор.
         Отдельная  стандартная  программа  обслуживания  прерывания (ISR)
         связывается с каждым видом прерывания.
              Давайте вернемся к примеру упрощенной системы управления до-
         машним хозяйством.  В нашем примере прерывания  разработаны  так,
         что работа процессора прерывается, когда:
            1. температура  выходит за  пределы диапазона от (ref_temp -
               - delta) до (ref_temp + delta)
            2. уровень дыма выходит за пределы безопасности.

              Процедуры adjust_temp и  initiate_alarm  представляют  собой
         две программы ISR,  соответствующие указанным двум видам прерыва-
         ний.
              В общем случае прерывание не может быть связано с процедурой
         water_lawn,  потому что эта процедура использует функцию прерыва-
         ния "int 21h" операционной системы MS-DOS для определения времени
         суток. Поскольку программа операционной системы MS-DOS не являет-
         ся реентерабельной, программа ISR не может произвести другое пре-
         рывание  в  операционной  системе   MS-DOS.   Поэтому   процедура
         water_lawn не разрабатывается в виде программы ISR, но вызывается
         из главного программного цикла main_loop:

          forever:
              call    water_lawn   ;
              call    wait_loop    ; для протекания некоторого времени

                                      - 5-26 -
                           ; до повторного вызова процедуры water_lawn
              jmp     forever

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

            1. Программа ISR использует команду IRET вместо команды
               RET в  обычной  процедуре.  Команда IRET осуществляет
               возврат управления в главную программу по завершении
               выполнения программы ISR.
            2. Вы должны сохранять регистры, используемые программой
               ISR. Регистры  должны восстанавливаться до выполнения
               команды IRET.
            3. Прерывания должны разрешаться/запрещаться.

              Программа adjust_temp  должна разрешать использование преры-
         ваний.  В противном случае сигнал об обнаружении дыма  может  ос-
         таться  нераспознанным,  что неприемлемо.  Аналогично,  программа
         initiate_alarm должна запрещать управления,  потому что она явля-
         ется  процедурой  наивысшего  приоритета  и не должна прерываться
         вплоть до возбуждения сигнала о пожаре.
              Указанные программы  ISR должны быть связаны с соответствую-
         щими уровнями прерываний.  Связь эта может быть реализована через
         функцию  "Установить  вектор  прерываний"  в операционной системе
         MS-DOS. Функция прерывания "int 21h" с кодом функции в AH=25h ис-
         пользуется для этой цели. DS:DX указывает на программу ISR до вы-
         полнения запроса.

                   mov     ah,25h        ; функция= установить вектор
                                         ;          прерываний
                   mov al,int_level      ; уровень  прерываний   в   AL
                   mov dx,seg adjust_temp
                   mov ds,dx             ; адрес программы ISR в DS:DX
                   mov dx,offset adjust_temp
                   int 21h               ; обращение к операционной
                                         ; системе DOS

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

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




                                      - 5-27 -
                             Циклические планировщики

              Для того,  чтобы  понять  работу  циклических  планировщиков
         рассмотрим систему управления процессом.  Требуется,  чтобы конт-
         роль и управление происходили бы каждые 100 мсек.  Другими харак-
         теристиками,  требующими контроля, являются давление, влажность и
         химический состав. Эти характеристики требуют менее частого конт-
         роля, чем температура процесса.
              Предположим, что процедура temp_control требует 10  мсек для
         контроля и управления температурой. Процедура temp_control должна
         выполняться каждые 100 мсек, потому что температуру следует конт-
         ролировать каждые 100 мсек.
              Аналогичным образом процедуры B,  C,  и D контролируют и уп-
         равляют тремя другими характеристиками, как это показано в табли-
         це 5-6. Далее, предположим, что процедуры C и D выполняются раз в
         каждые  300 мсек, а процедура  B  выполняется  два  раза   каждые
         300 мсек.

                                                         Таблица 5-6
                     Процедуры, требуемые для данного примера
         ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДД¬
         ¦               ¦ Контроли- ¦  Время   ¦ Требуемые повторы  ¦
         ¦ Имя процедуры ¦ руемая    ¦  выпол-  ГДДДДДДДДДДДДДДДДДДДДґ
         ¦               ¦ характе-  ¦  нения   ¦время   комментарий ¦
         ¦               ¦ ристика   ¦          ¦                    ¦
         ГДДДДДДДДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДґ
         ¦  temp_control ¦Температура¦ 20 мсек  ¦100 мсек  Изменения ¦
         ¦               ¦           ¦          ¦        не разрешены¦
         ¦  B            ¦ Давление  ¦ 40 мсек  ¦150 мсек  Изменения ¦
         ¦               ¦           ¦          ¦          разрешены ¦
         ¦  C            ¦ Влажность ¦ 60 мсек  ¦300 мсек  Изменения ¦
         ¦               ¦           ¦          ¦          разрешены ¦
         ¦  D            ¦ Химический¦ 38 мсек  ¦300 мсек  Изменения ¦
         ¦               ¦ состав    ¦          ¦          разрешены ¦
         АДДДДДДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДЩ

              Такая система реального времени может быть построена при ис-
         пользовании конструкции циклического планировщика, представленной
         блок-схемой 5-1.  Здесь имеется три цикла:  0,  1 и 2. Последова-
         тельность выполнения циклов такова: 0, 1, 2 и 0, 1, 2 повторно.
              Отметим, что  процедура  temp_control выполняется один раз в
         каждом цикле. Каждый цикл требует 100 мсек для своего выполнения,
         куда  включается время,  требуемое для выполнения главного цикла,
         время, требуемое для выполнения процедуры  temp_control  и  время
         "передышки",  появляющейся  в  конце  каждого цикла.  "Передышки"
         предназначены для синхронизации выполнения циклов с требованиями,
         предъявляемыми к временам, затрачиваемым на наиболее часто выпол-
         няемые процедуры (в нашем примере это относится к процедуре temp_
         control).

                                      - 5-28 -
         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         ¦                     ЪДДДДДДДДДД¬                         ¦
         ¦                     ¦          ¦                         ¦
         ¦                     ¦   СТАРТ  ¦                         ¦
         ¦                     ¦          ¦                         ¦
         ¦                     АДДДДВДДДДДЩ                         ¦
         ¦                          ¦                               ¦
         ¦                     ЪДДДДДДДДД¬                         ¦
         ¦                     ¦          ¦                         ¦
         ¦                     ¦счетчик=0 ¦                         ¦
         ¦                     ¦          ¦                         ¦
         ¦                     АДДДДВДДДДДЩ                         ¦
         ¦   ЪДДДДДДДДДДДДДДДДДДДДД>ґ                               ¦
         ¦   ¦             ЪДДДДДДДДДДДДДДДДДД¬                    ¦
         ¦   ¦             ¦счетчик= счетчик+1 ¦                    ¦
         ¦   ¦             ¦                   ¦                    ¦
         ¦   ¦             АДДДДДДДДВДДДДДДДДДДЩ                    ¦
         ¦   ¦                      ¦                               ¦
         ¦   ¦                 ЪДДДДДДДДД¬                         ¦
         ¦   ¦           0     ¦переход по¦  2                      ¦
         ¦   ¦       ЪДДДДДДДДД¦счетчику  ¦ДДДДДДД¬                 ¦
         ¦   ¦       ¦ цикл 0  ¦на MOD3   ¦цикл 2 ¦                 ¦
         ¦   ¦       ¦         АДДДДВДДДДДЩ       ¦                 ¦
         ¦   ¦       ¦            1 ¦цикл 1       ¦                 ¦
         ¦   ¦  ЪДДДДДДДДД¬   ЪДДДДДДДДД¬   ЪДДДДДДДДД¬          ¦
         ¦   ¦  ¦temp_cont-¦   ¦temp_cont-¦   ¦temp_cont-¦          ¦
         ¦   ¦  ¦rol       ¦   ¦rol       ¦   ¦rol       ¦          ¦
         ¦   ¦  ¦   20 мсек¦   ¦   20 мсек¦   ¦   20мсек ¦          ¦
         ¦   ¦  АДДДДВДДДДДЩ   АДДДДВДДДДДЩ   АДДДДВДДДДДЩ          ¦
         ¦   ¦       ¦              ¦              ¦                ¦
         ¦   ¦  ЪДДДДДДДДД¬   ЪДДДДДДДДД¬   ЪДДДДДДДДД¬          ¦
         ¦   ¦  ¦    B     ¦   ¦    C     ¦   ¦    В     ¦          ¦
         ¦   ¦  ¦    40мсек¦   ¦    60мсек¦   ¦    40мсек¦          ¦
         ¦   ¦  АДДДДВДДДДДЩ   АДДДДВДДДДДЩ   АДДДДВДДДДДЩ          ¦
         ¦   ¦       ¦              ¦              ¦                ¦
         ¦   ¦  ЪДДДДДДДДД¬        ¦              ¦                ¦
         ¦   ¦  ¦    D     ¦        ¦              ¦                ¦
         ¦   ¦  ¦    38мсек¦        ¦              ¦                ¦
         ¦   ¦  АДДДДВДДДДДЩ        ¦              ¦                ¦
         ¦   ¦       ¦              ¦              ¦                ¦
         ¦   ¦  ЪДДДДДДДДД¬   ЪДДДДДДДДД¬   ЪДДДДДДДДД¬          ¦
         ¦   ¦  ¦пауза на  ¦   ¦пауза на  ¦   ¦пауза на  ¦          ¦
         ¦   ¦  ¦   2 мсек ¦   ¦  20 мсек ¦   ¦ 40 мсек  ¦          ¦
         ¦   ¦  АДДДДВДДДДДЩ   АДДДДВДДДДДЩ   АДДДДВДДДДДЩ          ¦
         ¦   ¦                                                   ¦
         ¦   АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ                ¦
         ¦                      <-----                              ¦
         АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                Блок-схема 5-1. Структура циклического планировщика

              В блок-схеме  5-1 показана концепция построения циклического
         планировщика без прерываний.  Во многих прикладных программах мо-
         жет  оказаться  необходимым использование прерываний,  которые бы
         сигнализировали  о наличии внешнего события,  требующего  к  себе
         немедленного  внимания.  Циклический планировщик может быть также
         построен с применением требований,  что показано на рисунке 5-12.
              На рисунке  5-12  (A) показано,  как мы гарантируем то,  что
         главный цикл требует 100 мсек для выполнения каждого  цикла, если
         не  возникает  прерывания.  Предположим,  прерывание возникает во

                                      - 5-29 -
         время выполнения процедуры temp_control.  Процедура  temp_control
         завершается и после этого обрабатывается прерывание.  После обра-
         ботки прерывания выполняется процедура  B.  Поскольку  невозможно
         предсказать момент возникновения прерывания, общее время выполне-
         ния каждого цикла также не может быть предсказано. Таким образом,
         время  выполнения  цикла циклическим планировщиком с прерываниями
         является неопределяемым.
              Преимуществами конструкции циклического планировщика являют-
         ся:
            . Простая форма мультизадачности.
            . Определенность рабочего времени за исключением случаев
              с прерываниями.

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



                                                               
                ¦                ¦                 ¦    проис-  ¦
                ¦                ¦                 ¦    ходит   ¦
           ЪДДДДДДДДД¬          ¦            ЪДДДДДДДДД¬преры-¦
           ¦temp_cont-¦          ¦            ¦temp_cont-¦вание ¦
           ¦rol       ¦          ¦            ¦rol       ¦ДДДД ¦
           ¦   20 мсек¦          ¦            ¦   20 мсек¦      ¦
           АДДДДВДДДДДЩ          ¦            АДДДДВДДДДДЩ      ¦
                ¦                ¦                 ¦            ¦
           ЪДДДДДДДДД¬                       ЪДДДДДДДДД¬ общее вре-
           ¦    B     ¦     общее время       ¦прерывание¦ мя цикла
           ¦    40мсек¦    цикла = 100 мсек   ¦обслужено ¦ превышает
           АДДДДВДДДДДЩ                       ¦   20 мсек¦ 100 мсек
                ¦                ¦            АДДДДВДДДДДЩ из-за на-
           ЪДДДДДДДДД¬          ¦            ЪДДДДДДДДД¬ личия пре-
           ¦    D     ¦          ¦            ¦    B     ¦ рывания
           ¦    38мсек¦          ¦            ¦   40 мсек¦      ¦
           АДДДДВДДДДДЩ          ¦            АДДДДВДДДДДЩ      ¦
                ¦                ¦                 ¦            ¦
           ЪДДДДДДДДД¬          ¦            ЪДДДДДДДДД¬      ¦
           ¦пауза на  ¦          ¦            ¦    D     ¦      ¦
           ¦    2мсек ¦          ¦            ¦  38 мсек ¦      ¦
           АДДДДВДДДДДЩ          ¦            АДДДДВДДДДДЩ      ¦
                ¦                ¦                 ¦            ¦
                                                             
            А. Цикл 0, если преры-       Б. Цикл 0 при наличии пре-
               ваний не произошло           рываний (неопределяемый)
               (определяемый)
           Рис. 5-12.  Циклический планировщик при наличии  прерываний:
                            неопределяемое время цикла


                                      - 5-30 -
                          Выбор метода построения системы

              Выбор структуры системы зависит от внешней среды, аппаратных
         средств,  а также от временных требований, предъявляемых к систе-
         ме.  Выбор также зависит от того,  будут ли использоваться только
         уже имеющиеся в наличии технические средства или планируется  ис-
         пользование  новых  аппаратных средств.  Если будут приобретаться
         новые устройства,  то мы можем рассматривать  и  применение  уст-
         ройств, работающих под управлением прерываний, и устройств, рабо-
         тающих без  прерываний.  Если,  однако,  мы пользуемся только уже
         имеющимися аппаратными средствами,  мы не можем выбирать: исполь-
         зовать или нет прерывания.  Например,  некоторое устройство может
         не  предусматривать  работу  по прерыванию.  Тогда мы сами должны
         применить метод упорядоченного опроса для этого устройства.
              В некоторых  прикладных  программах  выбор может диктоваться
         требованиями, предъявляемыми к временным характеристикам, как это
         делается в простом примере,  предложенном  ниже. Принятие решения
         о выборе метода построения системы на практике, конечно, является
         более сложным занятием.
              Предположим,  что  мы  будем работать в операционной системе
         MS-DOS  под   управлением   процессора   8088,   имеющего частоту
         4,77 МГц.  Предположим также,  что нам нужно собирать  данные  со
         скоростью  35000 байтов в секунду.  Отметим по предыдущей таблице
         5-5, что при наличии прерываний  максимальная  скорость  передачи
         данных  равна  30 КГц.  Это значит,  что мы не можем использовать
         прерывания.  Тем не менее, системы опроса могут работать с макси-
         мальной  скоростью  передачи данных,  равной 60 КГц.  Более того,
         данные могут храниться при этом в основной памяти.
              И, наконец,  если требуется, чтобы разные процедуры выполня-
         лись с разной частотой, можно в этом случае применять циклические
         планировщики.
              Все три рассмотренных метода являются синхронными  (работаю-
         щими только с одной задачей,  то есть без мультизадачности). Ниже
         мы рассмотрим мультизадачный режим в операционной системе MS-DOS.

                Мультизадачный режим в операционной системе MS-DOS

              В общем  случае  операционная система MS-DOS не поддерживает
         работу в мультизадачном режиме,  несмотря на то, что операционная
         система  MS-DOS для персональных компьютеров IBM PC/AT предусмот-
         рены условия  работы в простом мультизадачном  режиме.  Мультиза-
         дачность  представляет собой очень мощное средство для систем ре-
         ального времени.  Оно  упрощает  структуру  системы  и  позволяет
         разрабатывать большие, сложные системы.
              Системы реального времени нацелены на  обработку  нескольких
         независимых событий, происходящих в произвольные моменты времени.
         Эти события могут возникать асинхронно и одновременно.  Это  зна-
         чит, что одно событие может происходить во время обработки друго-
         го.
              Мультизадачность может  использоваться  в таких системах для
         упрощения программного обеспечения.  Вместо  написания  отдельной
         программы  для управления всеми событиями вы можете написать  не-
         сколько программ, каждая из которых будет управлять отдельным со-
         бытием.  Все  эти программы могут одновременно выполняться компь-
         ютером,  поддерживающим мультизадачность. Эти отдельные программы
         называются  "задачами".  Поскольку они одновременно выполняются в
         компьютере, эта структура называется мультизадачной. Многие зада-

                                      - 5-31 -
         чи  выполняются  компьютером одновременно,  аналогично тому,  как
         жонглер удерживает много шаров в воздухе в одно и тоже время. А в
         действительности же,  в отдельный момент времени, конечно, выпол-
         няется только одна задача.

                  Условия существования мультизадачного режима в
                         персональном компьютере IBM PC/AT

               Система BIOS персонального компьютера IBM PC/AT  предусмат-
         ривает наличие "крюков" для применения планировщика. Поддерживае-
         мые функции являются очень простыми,  но могут использоваться для
         проектирования  и  реализации  программы,  поддерживающей простые
         возможности мультизадачности (обсуждение этих  возможностей лежит
         за пределами задач настоящей главы). Разработка общецелевого пла-
         нировщика в операционной системе MS-DOS  является  очень  трудным
         процессом,  поэтому  мы  рекомендуем  разрабатывать  планировщик,
         предназначенный только для вашей прикладной программы.
              Прерывание 15h  предусматривается  для поддержания мультиза-
         дачного планировщика.  Исходно планировщик настраивает  сервисную
         стандартную программу на обработку прерывания 15h.
              Планировщик может поддерживать такие  простые  функции,  как
         переключение задач и циклы простоев.
              Одно из средств персонального компьютера  IBM  PC/AT  должно
         реализовать  циклы простоя,  выдает прерывания 15h с шестнадцати-
         ричным значением кода функции "90 hex" в AH.  В этом месте плани-
         ровщик должен сохранить состояние текущей задачи. Эта схема дейс-
         твий позволяет выполнять задачи "с перекрытием" или " с наложени-
         ем", когда присутствует цикл простоя.
              Ожидающая задача может позднее возобновить свою  работу  при
         помощи планировщика, когда прерывание 15h с кодом функции 91h по-
         явится в AH. В этом месте планировщик должен будет запомнить, что
         задача будет готова возобновить свою работу позднее.
              Этим рассуждением завершается наше  изучение мультизадачного
         режима  в операционной системе MS-DOS.  Нам пришлось ограничиться
         коротким простым разговором,  потому что мультизадачный  режим  в
         операционной  системе  MS-DOS  для  персонального  компьютера IBM
         PC/AT очень невелик и трудно реализуем.

                                    Заключение

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

              Мы рассматривали скорость,  как требование операционной сис-
         темы VS-DOS, которое должно использоваться в прикладной программе
         реального времени. В дополнение к частоте синхронизации, скорость
         работы  операционной  системы  MS-DOS зависит от передачи данных.
         Используя  примеры,  мы  проиллюстрировали  использование  портов
         центрального  процессора  CPU  и  последовательных и параллельных
         портов для обмена данными с внешней средой. Мы также обсудили три
         основных метода передачи данных в компьютер и из него:

                                      - 5-32 -
            . Упорядоченный опрос
            . Метод прямого доступа DMA
            . Метод использования прерываний.

              Мы объяснили важность времени ответа во время принятия реше-
         ния  о  возможности  использования  операционной  системы MSDOS в
         прикладных программах реального времени.  Были описаны три  синх-
         ронных метода проектирования систем реального времени:
            . Упорядоченного опроса
            . Главного цикла с прерываниями
            . Циклического планирования.

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


                   Глава 6.  УСТАНАВЛИВАЕМЫЕ  ДРАЙВЕРЫ УСТРОЙСТВ


                   Зачем нужны драйверы устройств?
                   Установка драйверов устройств
                   Работа с драйвером в среде MS-DOS
                   Создание драйверов устройств
                   Пример драйвера виртуального диска
                   Заключение



            Основное требование, предъявляемое к любой вычислительной сис-
         теме,  заключается не только в способности вычислять, но и в спо-
         собности взаимодействовать с внешним миром через периферийные ус-
         тройства.  Без таковой способности компьютер становится не  более
         чем  бесполезной  "железякой".  Задача любой операционной системы
         состоит в обеспечении средств взаимодействия для прикладных прог-
         рамм и для нужд самой операционной системы.
            Для того, чтобы прикладная программа могла взаимодействовать с
         внешним  устройством,  операционная  система должна удовлетворять
         двум основным требованиям. Во-первых, должен существовать опреде-
         ленный  интерфейс между прикладной программой и операционной сис-
         темой.  Этот интерфейс должен быть достаточно гибким, чтобы  при-
         кладная программа могла точно определить свои действия при работе
         с требуемым устройством.  Во-вторых, операционная система обязана
         уметь передавать и принимать данные от устройства и управлять его
         работой.  Такой интерфейс в MS-DOS обеспечивается так называемыми
         драйверами устройств.
            В то время как операционные системы больших ЭВМ  и  миникомпь-
         ютеров  традиционно  обладают широкими возможностями по поддержке
         устройств,  микрокомпьютеры довольно бедны в этой области. Обычно
         они имеют средства поддержки основных дисковых накопителей,  сис-
         темного терминала, печатающего устройства и, возможно, какого-ли-
         бо дополнительного устройства. Все что поддерживается сверх этого
         уровня можно рассматривать как приятную неожиданность.  В  старых
         операционных  системах,  включая MS-DOS версии 1.0,  обеспечивать
         поддержку дополнительных устройств после покупки ОС было довольно
         затруднительно.  Операционная система не содержала функциональных
         запросов прикладного уровня для нестандартных  устройств  и  сами
         драйверы были глубоко запрятаны в BIOS (базовая система ввода-вы-
         вода).  Добавление или изменение  драйвера  устройства  требовало
         корректировки исходных кодов BIOS (при  их  наличии,  разумеется),
         повторного  ассемблирования и  копирования полученных кодов на за-
         грузочную дорожку системного диска.  Очень часто  для  выполнения
         указанных операций не было даже соответствующих утилит. Более то-
         го,  такие компьютеры как IBM PC не позволяли и  этого,  т.к.  их
         BIOS записана в ПЗУ (постоянном запоминающем устройстве). Измене-
         ние содержимого ПЗУ требует  наличия  специального  программатора
         (устройства,  которое  записывает  информацию  в  программируемое
         ПЗУ),  а он имеется далеко не у каждого.  И даже после всех  этих
         усилий  прикладная программа не имела никаких средств для общения
         с драйвером с помощью ОС.
            Все изменилось с выходом MS-DOS  версии  2.0. Вероятно,  самым
         значительным нововведением в операционных системах микрокомпьюте-
         ров с тех пор как появилась CP/M стало то,  что MS-DOS версии 2.0
         и выше обеспечивают не только возможность установки драйверов без

                                      - 6-2 -
         каких-либо мучений, но и стандартный расширяемый интерфейс, кото-
         рый дает программам возможность взаимодействовать с драйверами. В
         результате громадно возросло количество устройств, поддерживаемых
         MS-DOS   и  появились  драйверы  псевдоустройств,  обеспечивающие
         MS-DOS системы такими средствами как  RAM-диски,  высокоуровневые
         графические интерфейсы и т.п.
            Драйвер устройства в MS-DOS - это подпрограмма,  которая вызы-
         вается MS-DOS,  с одной стороны,  и взаимодействует с  конкретным
         устройством,  с другой.  Как посредник между системой и аппарату-
         рой,  драйвер устройства передает данные между программой и  уст-
         ройством.

                          Зачем нужны драйверы устройств?

            Драйверы устройств решают две основные задачи. Первая заключа-
         ется в обеспечении стандартного интерфейса со  всеми программами,
         желающими  использовать определенное устройство,  независимого от
         конкретных особенностей устройства.  Программа, выполняющая обра-
         ботку текста,  или электронная таблица,  производящая вычисления,
         может не заботиться о типе терминала,  подключенного  к  системе,
         выдавая простые команды типа "Отобразить символ" и "Получить сим-
         вол".  Все технические детали по пересылке символов берет на себя
         драйвер,  обеспечивая тем самым желанный для прикладной программы
         высокоуровневый интерфейс.  Замена терминала может вызвать замену
         драйвера,  но  при этом в прикладной программе не потребуется де-
         лать никаких изменений.  Драйверы дисководов должны  обеспечивать
         стандартный  интерфейс  для  всех используемых типов дисков,  при
         этом программа, осуществляющая ввод/вывод с диска, будет работать
         с дискетой любого формата,  с жестким диском, и даже с псевдодис-
         ком в ОЗУ,  не замечая никаких различий. Одним словом, первая за-
         дача  драйвера  состоит  в обеспечении независимого от устройства
         унифицированного интерфейса.
            Второе целевое  назначение  драйверов  устройств заключается в
         том,  что они для всех прикладных программ  обеспечивают  сервис,
         подобный библиотекам функций времени выполнения.  Любая программа
         освобождена не только от необходимости поддержки множества разно-
         форматных  устройств,  но  и от необходимости поддерживать вообще
         какие-либо форматы.  Все заботы по поддержке устройств  возложены
         на драйверы устройств.  В связи с тем, что все драйверы собраны в
         операционной системе, требуется лишь одна копия каждого драйвера.
         В результате этого программы,  написанные с использованием интер-
         фейса, предоставляемого MS-DOS, вообще не содержат в себе драйве-
         ров.
            В операционной системе MS-DOS версии 2.0 и выше драйверы могут
         быть добавлены для того,  чтобы заменить встроенные драйверы сис-
         темы. Если Вам не нравится как работает системный драйвер с конк-
         ретным устройством,  Вы можете написать свой собственный драйвер.
         Как подчеркивалось выше,  прикладные программы при этом ничего не
         заметят. Конечно  создание  драйвера  - не самое простое занятие,
         но, по крайней мере, такая возможность у Вас есть.
            Имея такое мощное средство обеспечения работы MS-DOS с различ-
         ными устройствами,  недолго представить себе драйверы, не поддер-
         живающие  реальных  устройств!  Другими  словами,  можно написать
         драйвер,который поддерживает несуществующее  устройство, например
         драйвер-эмулятор диска в ОЗУ.  Такие устройства получили название
         "виртуальные устройства",  а драйверы таких устройств,  соответс-
         твенно, "драйверы  виртуальных устройств" или просто "виртуальные

                                      - 6-3 -
         драйверы".
            Реальные или виртуальные устройства не ограничены, по сути де-
         ла,  только операциями ввода/вывода. На драйвер может быть возло-
         жена  любая  функция  по преобразованию данных.  Высокоскоростные
         процессоры для выполнения больших объемов вычислений  с плавающей
         точкой -  это  только  один из примеров устройства преобразования
         информации. Кроме того, драйверы могут программно эмулировать ре-
         альные устройства,  которые отсутствуют в конкретной системе, та-
         кие как часы или сопроцессор с плавающей точкой.

                      Когда использовать драйверы устройств?

            При каких  условиях некоторую функцию следует удалить из прог-
         раммы и перенести в драйвер?  Основное правило состоит в том, что
         если  какая-либо  функция  выполняет  ввод/вывод на на физическом
         уровне (т.е. работая непосредственно с аппаратурой), то эта функ-
         ция - прекрасный кандидат для переноса в драйвер. По самой приро-
         де семейства микропроцессоров 80x86 такие функции обычно содержат
         команды IN и/или OUT (включая INS или OUTS). Если система поддер-
         живает ввод/вывод,  отображенный на память,  доступ к  абсолютным
         адресам  памяти  также  может служить индикатором ввода/вывода на
         физическом уровне (чтение и запись векторов прерываний тоже явля-
         ется доступом к абсолютным адресам, но, конечно, предпочтительнее
         использовать функции MS-DOS "Получить вектор прерывания" и "Уста-
         новить  вектор  прерывания",  чем  использовать для этих же целей
         драйвер).
            Выделение программ-обработчиков  операций ввода/вывода в драй-
         вер устройства порождает четыре следствия :  это делает программы
         более  переместимыми,  делает  обработчики  операций ввода/вывода
         доступными для других программ, желающих работать с этим устройс-
         твом,  несколько увеличивает в размерах систему и замедляет время
         доступа к аппаратуре.  Некоторое увеличение размера памяти, зани-
         маемой  системой,  не  имеет большого значения,  а вот увеличение
         времени доступа может быть  критическим  фактором  для  некоторых
         приложений. Когда принимается решение о написании драйвера, необ-
         ходимо тщательно взвесить скоростные характеристики программы,  с
         одной стороны,  и  повышение совместимости программ и доступность
         драйвера, с другой стороны. Увеличение времени  доступа  за  счет
         накладных  расходов при каждом обращении к драйверу более заметно
         для устройств,  которые передают за один раз слово или байт  дан-
         ных. В драйверах, передающих за одно обращение целый блок данных,
         накладные расходы заметно уменьшаются.

                        MS-DOS - нереентерабельная система

            В связи с тем,  что обращения к драйверам осуществляет MS-DOS,
         на них накладываются такие же ограничения, как и на резидентные в
         памяти программы.  Так,  например, драйверы не могут пользоваться
         функциями MS-DOS (за исключением некоторых функций, которые могут
         использоваться при инициализации драйвера).  Это серьезно ограни-
         чивает свободу драйверов виртуальных устройств, созданных для до-
         полнительной обработки информации,  предназначенной для стандарт-
         ных драйверов.
            Так, например,  драйвер виртуального принтера, предназначенный
         для поддержки графических примитивов на  игольчатом  принтере  не
         может  использовать  стандартные функции MS-DOS для вывода симво-
         лов.  Драйвер виртуального принтера  должен  обеспечивать  полную

                                      - 6-4 -
         программную  поддержку  для  осуществления  физического вывода на
         принтер. Заметим, что драйвер, описанный в этом примере, именует-
         ся виртуальным, несмотря на то, что он работает с физическим уст-
         ройством.  Это объясняется тем, что драйвер предоставляет возмож-
         ности,   не   поддерживаемые   реальным  устройством,  такие  как
         выполнение графических операций на простом принтере.
            В связи с тем,  что MS-DOS нереентерабельна,  нельзя использо-
         вать программу DEBUG для отладки установленного драйвера. Для вы-
         полнения   собственных  операций  ввода/вывода  DEBUG  использует
         MS-DOS и если DEBUG использовать для отладки драйвера,  он испор-
         тит  переданную  драйверу  информацию,  делая невозможным возврат
         корректной информации в MS-DOS.  Один из  способов  обхода  этого
         препятствия  заключается в использовании любых имеющихся встроен-
         ных функций ввода/вывода (например,  функций BIOS) для вывода от-
         ладочной информации.  Более предпочтительный способ заключается в
         создании небольшой тестовой программы для проверки работы драйве-
         ра, которая передает драйверу тестовые данные и проверяет возвра-
         щаемую информацию.  Такая программа запускается  под  управлением
         отладчика обычным образом.  Конечно,  если устройство критично ко
         времени,  необходимо принять соответствующие меры, чтобы избежать
         какого-либо влияния на работу драйвера.

                           Установка драйверов устройств

            Как упоминалось ранее,  во времена, предшествующие MS-DOS вер-
         сии  2.0,  установка драйвера устройства означала изменение BIOS.
         Начиная с версии 2.0 появилась возможность устанавливать и  заме-
         нять драйверы в процессе начальной загрузки системы.
            Процесс начальной загрузки MS-DOS начинается со сброса  систе-
         мы.  Аппаратура  Вашей системы устанавливается в состояние сброса
         при включении питания компьютера.  Сразу после  сброса  процессор
         начинает выполнять команды,  находящиеся в самом конце его адрес-
         ного пространства.  Для процессора 80386 это команды, находящиеся
         по  шестнадцатиричному  адресу FFFFFFF0,  для процессора 80286 по
         адресу FFFFF0,  для процессора 8086 по адресу FFFF0. В любом слу-
         чае  по этим адресам находится ПЗУ,  содержащее начальный загруз-
         чик,  задача которого заключается в  загрузке  системной  области
         диска  в память.  Интересно отметить,  что возможности начального
         загрузчика постоянно росли.  Первый персональный компьютер  фирмы
         IBM  (IBM  PC)  мог загружаться только с дисковода "A".  Вместе с
         компьютером IBM PC XT появилась возможность загрузки  с  жесткого
         диска  и, видимо, недалек тот час, когда появится возможность се-
         тевой загрузки.
            Системная область  диска,  загружаемая в память начальным заг-
         рузчиком,  называется вторичным загрузчиком. В случае MS-DOS, ра-
         ботающей на IBM - совместимом компьютере, это самый первый сектор
         диска длиной 512 байт.  Такой маленький  размер  объясняется  тем
         фактом,  что BIOS находится в ПЗУ.  Вторичному загрузчику, в этом
         случае, для загрузки остальной части системы достаточно обратить-
         ся к BIOS,  которая всегда находится в ПЗУ. В системах, не содер-
         жащих BIOS в ПЗУ,  начальный загрузчик должен считывать  с  диска
         программу, способную обеспечить возможность вторичному загрузчику
         считать остальную часть системы.   В таких системах начальный за-
         грузчик должен считывать довольно большую часть диска.
            Сама MS-DOS загружается только после того,  как будет считан в
         память  вторичный загрузчик.  Именно по этой причине возможен за-
         пуск игр, не требующих для своей работы MS-DOS, или возможна заг-

                                      - 6-5 -
         рузка  других  операционных систем.  Собственно,  тип загружаемой
         системы зависит от того,  что именно считывается  с  загрузочного
         диска. При загрузке MS-DOS вторичный загрузчик предполагает нали-
         чие на диске корневого директория и,  как минимум, двух системных
         файлов. В связи с тем, что эти файлы скрытые, они не отображаются
         при выводе содержимого корневого  директория  (однако,  их  можно
         увидеть при помощи таких утилит,  как XTREE, Norton Utilities или
         SDIR).  Функции этих файлов одинаковы у  всех  поставщиков,  хотя
         имена могут различаться. Первый файл содержит ядро MS-DOS и обыч-
         но называется MSDOS.SYS или IBMDOS.COM  на  системах  фирмы  IBM.
         Другой  файл  содержит  интерфейс между MS-DOS и подсистемой вво-
         да-вывода и называется IO.SYS (Microsoft),  IBMBIO.COM (IBM)  или
         еще как-нибудь у других поставщиков. Вместе эти два файла состав-
         ляют операционную систему MS-DOS.  После того, как вторичный  за-
         грузчик находит и загружает эти файлы, начинается процесс инициа-
         лизации  MS-DOS.  Заметим,  что   на   IBM-совместимых   системах
         вторичный загрузчик считывает только файл IBMBIO.COM,  который, в
         свою очередь, загружает IBMDOS.COM.
            Как только  загружен интерфейсный файл (IO.SYS или его эквива-
         лент),  вторичный загрузчик передает управление процедуре инициа-
         лизации,  содержащейся в интерфейсном файле. Кроме этой процедуры
         интерфейсный файл содержит стандартные  драйверы,  которые  будут
         использоваться при инициализации и работе MS-DOS.
            Сама процедура инициализации заключается в распределении  час-
         тей MS-DOS в памяти, создании всех внутренних таблиц, рабочих об-
         ластей и т.п.,  и, наконец, инициализации всех устройств, связан-
         ных  с  системой.  Инициализация  устройств заключается в посылке
         команды INIT каждому из драйверов,  содержащихся  в  интерфейсном
         файле (мы обсудим команду INIT позже,  совместно с другими коман-
         дами для драйверов устройств). После инициализации устройств про-
         цедура  инициализации  заканчивает  создание  внутренних таблиц и
         система к этому моменту готова к работе. До окончательного завер-
         шения, однако, остается еще один шаг.
            В этой точке процедура инициализации проверяет  наличие  файла
         CONFIG.SYS.  Если указанный файл отсутствует, то MS-DOS загружает
         стандартный интерпретатор команд и передает ему  управление. Если
         же файл CONFIG.SYS найден,  то выполняется еще один шаг инициали-
         зации. На этом этапе Вам предоставляется возможность подключить к
         MS-DOS Ваши собственные драйверы устройств.

                                  Файл CONFIG.SYS

            Файл CONFIG.SYS это обычный  текстовый  файл,  который  должен
         быть расположен в корневом директории диска,  с которого происхо-
         дит загрузка системы (если этот файл находится не в корневом  ди-
         ректории,  то процедура инициализации предполагает, что он совсем
         отсутствует).  Файл CONFIG.SYS содержит  команды,  руководствуясь
         которыми  процедура  инициализации изменяет и/или дополняет стан-
         дартную конфигурацию MS-DOS.  Если этот файл доступен,  процедура
         инициализации  (но не COMMAND.COM - он еще не загружен) считывает
         его в память и обрабатывает строка за строкой.  Каждая строка со-
         держит одну команду конфигурации. На диаграмме 6-1 показана обра-
         ботка некоторых команд.  Наиболее важна для нас  команда  DEVICE,
         которая имеет следующий формат:

                DEVICE=[d:][path]filename[.ext][ parameters]


                                      - 6-6 -
            где (заключенные в квадратные скобки элементы не являются обя-
         зательными):
                d:         - идентификатор дисковода,
                path       - путь к драйверу,
                filename   - имя файла, содержащего драйвер,
                ext        - расширение имени файла,
                parameters - параметры для драйвера.

            Эта команда  задает  необходимость  установки нового драйвера.
         Программа драйвера,  содержащаяся в заданном драйвере,  похожа на
         обычную .COM программу,  но имеет некоторые специфические особен-
         ности, описываемые далее,  в разделе, посвященном написанию драй-
         веров.
            В общем случае,  драйвер представляет собой особую форму рези-
         дентной программы.  Когда в файле CONFIG.SYS встречается  команда
         DEVICE, соответствующий драйвер загружается в память и анализиру-
         ется. Заголовок драйвера содержит информацию о типе, имени, атри-
         бутах устройства и определяет точки входа в программу. После заг-
         рузки  драйвера  MS-DOS  обращается  к  драйверу с командой INIT.
         Драйвер выполняет инициализацию и возвращает  управление  MS-DOS,
         указывая адрес конца драйвера, т.е. адрес первого свободного бай-
         та памяти, непосредственно следующего за драйвером. На этом уста-
         новка драйвера заканчивается.
            Указание адреса  конца  драйвера  при  возвращении  управления
         MS-DOS после выполнения команды INIT подобно указанию размера па-
         мяти,  занимаемой программой, при вызове функции MS-DOS "Остаться
         резидентом". По возвращаемому адресу MS-DOS определяет расположе-
         ние свободной памяти. Если файл CONFIG.SYS содержит другие коман-
         ды  DEVICE,  следующий  драйвер загружается непосредственно после
         предыдущего.  После того, как обработка файла CONFIG.SYS законче-
         на,  загружается еще один драйвер - драйвер фиктивного устройства
         (NUL-драйвер).  Затем MS-DOS  завершает  инициализацию  загрузкой
         постоянной части COMMAND.COM или другой, определяемой пользовате-
         лем оболочки.
            При загрузке драйверов MS-DOS связывает их в цепочку, так что-
         бы каждый драйвер содержал ссылку на ранее  загруженный  драйвер.
         Цепочка драйверов начинается,  таким образом, с последнего загру-
         женного драйвера (NUL-драйвер) и заканчивается самым первым  заг-
         руженным  драйвером (обычно стандартный драйвер устройства COM2).
         Такая цепочка строится, используя первые два слова заголовка каж-
         дого драйвера.  Эти два слова содержат сегмент и смещение следую-
         щего в цепочке драйвера или, в случае последнего драйвера число -
         1 (шестнадцатиричное значение FFFF). Пример цепочки драйверов по-
         казан в листинге 6-6, приведенном в конце этой главы.
            Когда MS-DOS  требуется  обратиться  к определенному драйверу,
         она начинает поиск по цепочке драйверов (начиная  с NUL-драйвера)
         в порядке, обратном тому, в котором драйверы были загружены. Пос-
         ле того, как требуемый драйвер найден, MS-DOS обращается к нему с
         соответствующей командой. Последовательность поиска в цепочке при
         этом такова,  что если загружен пользовательский драйвер, имя ко-
         торого  совпадает с именем какого-либо стандартного драйвера (та-
         кого как CON,  AUX или PRN),  драйвер пользователя  будет  найден
         первым.  Это позволяет пользователю заменять стандартные драйверы
         (например, заменить стандартный CON-драйвер на ANSI.SYS CON-драй-
         вер).
            Стандартные драйверы в действительности загружаются и  инициа-
         лизируются  до того как файл CONFIG.SYS будет считан и обработан.

                                      - 6-7 -
           ЪДДД¬   ЪДДД¬
           ¦ 1 ¦   ¦ 2 ¦
           АДВДЩ   АДВДЩ
             АДДДВДДДЩ
                 
               ЪДДД¬
               ¦ 3 ¦
               АДВДЩ
                / \   Да
              <  4  >ДДДДДДДД>В<ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
                \ /                                                  ¦
                 ¦Нет        / \  Да                                  ¦
                 ¦         <  5  >ДДДДДДДДДДДДДДДДДДДДДДДДДДД¬        ¦
                 ¦           \ /                                     ¦
                 ¦<ДДДДДДДДДДДЩНет                         ЪДДД¬      ¦
                                                          ¦ 7 ¦      ¦
               ЪДДД¬                                       АДВДЩ      ¦
               ¦ 6 ¦                                    Да  / \       ¦
               АДВДЩ                              ЪДДДДДДД<  8  >     ¦
                 ¦                                         \ /       ¦
                 ¦                              ЪДДД¬        ¦Нет     ¦
                / \  Да                         ¦ 9 ¦                ¦
              <  12 >ДДДДДДДДД¬                 АДВДЩ      ЪДДД¬      ¦
                \ /                                      ¦ 10¦      ¦
                 ¦Нет       ЪДДД¬               ЪДДД¬      АДВДЩ      ¦
                           ¦ 13¦               ¦ 11¦        ¦        ¦
               ЪДДД¬        АДВДЩ               АДВДЩ                ¦
               ¦ 14¦          ¦                   АДДДДДДДДДДДДДДДДДДДЩ
               АДВДЩ          ¦
                 ¦            ¦
                 ¦<ДДДДДДДДДДДЩ
                 ¦
                 
               ЪДДД¬
               ¦ 15¦
               АДДДЩ

                   Блок-схема 6-1. Процесс инициализации MS-DOS.

            1 - Теплая загрузка (при нажатии клавиш Ctrl+Alt+Del)
            2 - Холодная загрузка (кнопка "СБРОС" или включение питания)
            3 - Загрузка системы
            4 - Файл CONFIG.SYS существует ?
            5 - Есть еще строки в CONFIG.SYS ?
            6 - Загрузка и запуск требуемого командного процессора (по
                умолчанию это COMMAND.COM)
            7 - Чтение строки из файла CONFIG.SYS
            8 - Это команда "DEVICE=" ?
            9 - Загрузка указанного в команде "DEVICE=" файла и подключение
                его к цепочке драйверов
           10 - Обработка какой-либо из команд "BREAK=","BUFFERS=","FILES="
                или "SHELL="
           11 - Обращение к драйверу с командой "INIT="
           12 - Файл AUTOEXEC.BAT существует ?
           13 - Выполнение всех обнаруженных в AUTOEXEC.BAT команд
           14 - Выполнение программ DATE и TIME
           15 - Выдача системного приглашения "A:>"

                                      - 6-8 -
         Это позволяет процедуре инициализации драйвера использовать неко-
         торые  функции MS-DOS для вывода сообщений или настройке драйвера
         на конкретную версию операционной системы.  Без всякой опаски мо-
         гут быть использованы функции MS-DOS с 01H по 0CH,  которые обес-
         печивают  работу  с  устройствами CON, PRN и AUX, а также функция
         30H ("Получить версию MS-DOS").  Вызовов,  относящихся к работе с
         файлами или управлением памятью,  следует избегать, так как  рас-
         пределение памяти полностью еще не завершено.
            После того, как файл CONFIG.SYS обработан и драйверы проиници-
         ализированы, стандартные драйверы устройств CON, PRN и AUX закры-
         ваются и заново открываются операционной системой для того, чтобы
         могла  произойти  замена  (если  таковая предусмотрена) указанных
         драйверов.  Начиная с этого  момента  используются  только  новые
         драйверы.
            Определенные драйверы не могут  быть  заменены  пользователем.
         Один из них - это драйвер пустого  (фиктивного)  устройства  NUL.
         Это  объясняется тем фактом,  что MS-DOS использует NUL-драйвер в
         качестве начала цепочки драйверов. Так как встроенный NUL-драйвер
         всегда определяет начало цепочки драйверов,  то первым всегда бу-
         дет найден встроенный NUL-драйвер.  Схематический пример  цепочки
         драйверов показан на рисунке 6-1.  Подробно назначение каждого из
         указанных полей будет объяснено позже.  Драйвер,  помеченный  как
         последний,  в действительности был первым устанавливаемым драйве-
         ром,  а драйвер, находящийся сразу после NUL-драйвера (в цепочке)
         устанавливался самым последним.
             ЪДДДДДДДДДДДД·         ЪДДДДДДДДДДДД·         ЪДДДДДДДДДДДД·
             ¦ Указатель  є         ¦ Указатель  є         ¦   Маркер   є
             ¦ на первый  ЗДДДДДДДД>¦    на      ЗДДДДДДДД>¦ последнего є
             ¦  драйвер   є         ¦ следующий  є         ¦  драйвера  є
             ¦            є         ¦  драйвер   є         ¦   ( -1 )   є
             ГДДДДДДДДДДДД¶         ГДДДДДДДДДДДД¶         ГДДДДДДДДДДДД¶
             ¦  Атрибуты  є         ¦  Атрибуты  є         ¦  Атрибуты  є
             ГДДДДДДДДДДДД¶         ГДДДДДДДДДДДД¶         ГДДДДДДДДДДДД¶
             ¦ Указатель  є         ¦ Указатель  є         ¦ Указатель  є
       ЪДДДДДґ     на     є   ЪДДДДДґ     на     є   ЪДДДДДґ     на     є
       ¦     ¦ СТРАТЕГИЙ  є   ¦     ¦ СТРАТЕГИЙ  є   ¦     ¦ СТРАТЕГИЙ  є
       ¦     ГДДДДДДДДДДДД¶   ¦     ГДДДДДДДДДДДД¶   ¦     ГДДДДДДДДДДДД¶
       ¦     ¦ Указатель  є   ¦     ¦ Указатель  є   ¦     ¦ Указатель  є
       ¦  ЪДДґ     на     є   ¦  ЪДДґ     на     є   ¦  ЪДДґ     на     є
       ¦  ¦  ¦ ПРЕРЫВАНИЙ є   ¦  ¦  ¦ ПРЕРЫВАНИЙ є   ¦  ¦  ¦ ПРЕРЫВАНИЙ є
       ¦  ¦  ГДДДДДДДДДДДД¶   ¦  ¦  ГДДДДДДДДДДДД¶   ¦  ¦  ГДДДДДДДДДДДД¶
       ¦  ¦  ¦ Устройство є   ¦  ¦  ¦  Имя или   є   ¦  ¦  ¦  Имя или   є
       ¦  ¦  ¦    NUL     є   ¦  ¦  ¦   число    є   ¦  ¦  ¦   число    є
       ¦  ¦  ¦            є   ¦  ¦  ¦ устройств  є   ¦  ¦  ¦ устройств  є
       АДДДД>ГДДДДДДДДДДДД¶   АДДДД>ГДДДДДДДДДДДД¶   АДДДД>ГДДДДДДДДДДДД¶
          ¦  ¦ Программа  є      ¦  ¦ Программа  є      ¦  ¦ Программа  є
          ¦  ¦ СТРАТЕГИЙ  є      ¦  ¦ СТРАТЕГИЙ  є      ¦  ¦ СТРАТЕГИЙ  є
          ¦   \/\/\/\/\/\/       ¦   \/\/\/\/\/\/       ¦   \/\/\/\/\/\/
          ¦  /\/\/\/\/\/\/\      ¦  /\/\/\/\/\/\/\      ¦  /\/\/\/\/\/\/\
          АД>ГДДДДДДДДДДДД¶      АД>ГДДДДДДДДДДДД¶      АД>ГДДДДДДДДДДДД¶
             ¦ Программа  є         ¦ Программа  є         ¦ Программа  є
             ¦ ПРЕРЫВАНИЙ є         ¦ ПРЕРЫВАНИЙ є         ¦ ПРЕРЫВАНИЙ є
              \/\/\/\/\/\/           \/\/\/\/\/\/           \/\/\/\/\/\/
             /\/\/\/\/\/\/\         /\/\/\/\/\/\/\         /\/\/\/\/\/\/\
             ФННННННННННННј         ФННННННННННННј         ФННННННННННННј
                     Рисунок 6-1. Цепочка драйверов устройств.
            Не только NUL-драйвер не может быть заменен. Драйверы, работа-

                                      - 6-9 -
         ющие с устройствами массовой памяти (например с  дисками),  также
         не  могут  быть  заменены.  Вы можете добавить драйверы для новых
         дисков,  но не удалить или заменить уже существующие. Это ограни-
         чение возникает по той причине, что имена драйверам дисковых уст-
         ройств (A,B,C и т.д.) назначает MS-DOS при  загрузке.  Невозможно
         присвоить  конкретному  дисководу уникальное имя,  соответственно
         нельзя и заменить его.

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

            Тем не менее не расстраивайтесь, если Вас не удовлетворяет ра-
         бота существующих дисковых драйверов. Хотя их нельзя удалить, они
         могут быть "нейтрализованы". После того, как Вы написали (и прове-
         рили) новый драйвер,добавьте его в файл CONFIG.SYS. После  переза-
         грузки системы он будет включен в цепочку драйверов устройств. На-
         пример, если Вы имеете три дисковода,  новый драйвер  получит  имя
         "D".  Теперь  используйте  команду ASSIGN для переназначения любых
         обращений к старому драйверу на новый. Допустим, мы хотим заменить
         драйвер дисковода "A". Команда ASSIGN, при этом, будет иметь вид

                ASSIGN A = D

            MS-DOS переназначит  все  обращения  к драйверу "A" на драйвер
         "D",  включая абсолютный доступ к диску по прерываниям 25H и 26H.
         Если  Вы  написали  новый  драйвер для работы с тем же физическим
         дисководом, с которым работал старый драйвер, то описанной проце-
         дурой  Вы довольно эффективно заменили его на новый.  Если же Вам
         покажется, что старый драйвер все-таки лучше, Вы можете восстано-
         вить первоначальную конфигурацию,  введя команду ASSIGN без пара-
         метров.
        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
        ¦    ПРЕДУПРЕЖДЕНИЕ : Когда НЕЛЬЗЯ использовать команду ASSIGN    ¦
        ¦                                                                 ¦
        ¦    Хотя  команда  ASSIGN  позволяет  Вам  заменять существующие ¦
        ¦ драйверы  дисков  на  новые,  это  не всегда разумно. Некоторые ¦
        ¦ команды,  такие  как  BACKUP  и  PRINT, или программы, подобные ¦
        ¦ Lotus 1-2-3 будут весьма удивлены, если их попросят работать  с ¦
        ¦ переопределенными дисками.  Другие команды,  такие как  FORMAT, ¦
        ¦ DISKCOPY или DISKCOMP, вообще игнорируют такие диски и работают ¦
        ¦ с настоящими логическими дисками.                               ¦
        АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

                             Типы драйверов устройств

            Существует два типа драйверов устройств,  именованные и неиме-
         нованные,  называемые соответственно драйверами  символьных  уст-
         ройств  и драйверами блоковых устройств.  Различие между ними го-
         раздо глубже,  чем способность иметь имя или заменяемость.  Кроме
         того,  что блоковые драйверы предназначены для поддержки дисковых
         устройств, предполагается,что один блоковый драйвер может поддер-
         живать  более  одного  дисковода.  Команды ввода/вывода для таких
         драйверов обеспечивают возможность доступа к  отдельным  секторам
         и,  если  не  задан  атрибут NONIBM (также известный как NONFAT),
         предполагается,  что драйвер должен поддерживать стандартную  для
         MS-DOS структуру диска, включая FAT (таблицу распределения диска)

                                      - 6-10 -
         и директории.
            Откровенно говоря,  названия "символьный" и "блоковый" не сов-
         сем точны,  так как символьный драйвер  тоже  может  поддерживать
         блоковый режим передачи данных.  Более того,  нельзя сказать, что
         символьные драйверы обеспечивают последовательный доступ, а блоч-
         ные драйверы обеспечивают прямой доступ,  так как можно спроекти-
         ровать символьный драйвер так, чтобы он поддерживал прямой доступ
         к устройству (если, конечно, он может работать в таком режиме.
            Оставив пока вопрос о том,  что же такое символьный драйвер  и
         что  такое  блоковый драйвер,  обсудим некоторые способы работы с
         драйверами устройств через MS-DOS. Это даст нам некоторые сообра-
         жения о том,  какой тип следует выбрать, если Вы желаете написать
         драйвер для какого-либо приложения.

                   Работа с драйверами устройств в среде MS-DOS

            Для прикладных  программ  MS-DOS  обеспечивает четыре основных
         метода доступа к внешним устройствам.  Каждый из них  удобен  для
         соответствующих  приложений и мы обсудим достоинства и недостатки
         каждого метода для того,  чтобы Вы могли выбрать метод,  наиболее
         удобный для Вашего приложения.  Мы не будем описывать детали каж-
         дого из функциональных вызовов, так как эту информацию можно най-
         ти в "MS-DOS Programmer's Reference Manual" ("MS-DOS. Руководство
         программиста.") фирмы Microsoft или другом аналогичном  руководс-
         тве. Следующий ниже список классифицирует эти четыре метода.

         * CP/M-ориентированные  функции для работы с такими устройствами,
           как консоль, принтер или вспомогательное устройство. Это истин-
           но символьные устройства. Функции, входящие в эту группу :

                CON: Функции 01H, 02H, и с 06H по 0CH
                PRN: Функция 05H
                AUX: Функции 03H и 04H

         * CP/M-ориентированные функции для работы с файлами с использова-
           нием FCB (блока управления файлами).  Этот  метод  также  может
           быть  использован  для доступа к символьным устройствам.  В эту
           группу входят функции :

             Открыть/Закрыть:                 Функции 0FH и 10H
             Читать/Писать Устройство/Файл:   Функции 14H и 15H
             Читать/Писать Файл:              Функции 21H, 22H, 27H и 28H

         * Функции MS-DOS-стиля для  работы  с  файлами  с  использованием
           описателей. Этот метод (аналогично FCB-методу) тоже можно ис-
           пользовать для работы с символьными устройствами.  Функции, ра-
           ботающие с использованием описателей файлов :

                Открыть/Закрыть:                 Функции 3DH и 3EH
                Читать/Писать Устройство/Файл:   Функции 3FH и 40H
                Управление Устройством:          Функция 44H
         * Функции прямого доступа к диску, выполняющие чтение и запись по
           абсолютным адресам.  Эти функции обеспечиваются отдельными пре-
           рываниями INT 25H (абсолютное чтение) и INT 26H (абсолютная за-
           пись).


                                      - 6-11 -
             Функции CP/M-стиля для работы с символьными устройствами

            CP/M-ориентированные функции предназначены,  в  основном,  для
         работы со стандартным устройством CON и предлагают возможности для
         буферизации, эхо-отображения, ожидания символов и проверки состоя-
         ния.  Поддержка устройств PRN и AUX более ограниченная,  но вполне
         достаточна для многих приложений. Для нестандартных устройств, од-
         нако,  необходимо использовать либо метод, использующий FCB (блоки
         управления файлами), либо метод на основе описателей файлов.

          Работа с устройством с использованием блоков управления файлами

            FCB-метод работы с устройствами имеет и достоинства  и  недос-
         татки. С одной стороны, FCB сложнее создавать и использовать, чем
         работать с  описателями файлов,  хотя использование макросредств и
         директив STRUC может весьма облегчить задачу построения блока  уп-
         равления  файлом.  С другой стороны,  FCB-метод позволяет програм-
         мисту непосредственно указывать номер записи в файле,  делая  воз-
         можным  прямой  доступ  к  файлам.  Функции  3FH  ("Читать") и 40H
         ("Писать"), работающие с описателями, позволяют осуществлять толь-
         ко последовательный доступ к файлам. Для выполнения прямого досту-
         па к файлам,  используя функции описателей ,  прикладная программа
         должна  обращаться  к функции 42H ("Передвинуть указатель файла").
         FCB-метод работы таких дополнительных действий не требует.

                 Работа с устройствами на основе описателей файлов

            Хотя прямой доступ очень нужен при работе  с  файлами,  он  не
         имеет большого значения при работе с  не  дисковыми  устройствами.
         При  работе  с  такими  устройствами  метод доступа,  использующий
         описатели,  намного проще в использовании и не требует от програм-
         миста  создания FCB.  Кроме того,  описатель-ориентированный метод
         доступа  (ДОМД)  поддерживает  IOCTL  (управление  вводом/выводом)
         функцию 44H. Как мы вскоре увидим, IOCTL-функция может быть исклю-
         чительно полезна для управления устройством.
            При использовании   ДОМД   (описатель-ориентированного   метода
         доступа) для работы с не дисковыми  устройствами,  программист  не
         ограничен пересылкой одного байта за один раз. За одно обращение к
         функциям ввода/вывода может быть переслано  с  устройства  или  на
         устройство до 64 Kбайт.  Как и при работе с дисками, использование
         этих функций для  не  дисковых  устройств  приводит  к  выполнению
         последовательной передачи данных. Используя, однако, IOCTL-функцию
         прямого управления,  можно задать устройству дополнительные  пара-
         метры.  Так, например, если и устройство и его драйвер установлены
         в режим прямого доступа,  можно использовать IOCTL-функцию для уп-
         равления  точками  отправления и назначения при пересылке данных в
         устройстве.
            Этот пример может помочь при  иллюстрации  потенциала  прямого
         управления вводом/выводом с устройством. Предположим, что некото-
         рая система имеет отображаемую на адресное пространство графичес-
         кую подсистему.  Данные из системной памяти в графическую пересы-
         лаются с использованием драйвера графического  адаптера.  По  той
         причине,  что этот адаптер не является устройством массовой памя-
         ти, драйвер для него должен быть символьным. Если ввод/вывод про-
         изводится с использованием только ДОМД,  нет никакого способа оп-
         ределить место в видео-памяти,  куда должны быть посланы  данные.

                                      - 6-12 -
         Если  же драйвер поддерживает IOCTL-функцию,  место в графической
         памяти можно определить через канал управления.

           Функция 44H - управление вводом/выводом для устройств (IOCTL)

            Как мы   упоминали,   не  все  устройства  поддерживают  вызов
         IOCTL-функции.  Те драйверы, которые обеспечивают управление вво-
         дом/выводом,   не   обязательно   поддерживают   все  возможности
         IOCTL-функции.  Тем не менее,  IOCTL  является  настолько  мощным
         средством  управления  работой  устройств,  что  понуждает многих
         программистов поближе познакомиться с его  возможностями.  Знание
         того,  что  можно сделать с помощью IOCTL,  несомненно определяет
         решение программиста о том,  какими функциональными особенностями
         наделить драйвер устройства.
            Функция управления вводом/выводом имеет три  основных  режима,
         которые определяются передаваемым в регистре AL кодом функции :

              - Конфигурация устройства (коды 0, 1 и в последних версиях
                MS-DOS, коды 8, 0BH, 0EH и 0FH);
              - Управление каналом ввода/вывода (коды с 2 по 5 и в MS-DOS
                версии 3.2, коды 0CH и 0DH);
              - Запрос статуса устройства (коды 6 и 7).

         Список кодов функций, поддерживаемых IOCTL, показан в таблице 6-1.
            Запрос статуса устройства возвращает либо индикатор готовности
         (0FFH) либо не готовности (0). В руководстве  программиста  фирма
         Microsoft предупреждает о том,  что код статуса может быть некор-
         ректным на момент возвращения  управления  вызывающей  программе.
         Вероятно, в руководстве имеется в виду будущая возможность  муль-
         тизадачности MS-DOS.  Можно только надеяться,  что когда появятся
         будущие версии, Microsoft найдет способ возвращать корректную ин-
         формацию. Как бы то ни было, до тех пор пока MS-DOS не стала мно-
         гозадачной, проблемы неточности статуса не должно существовать.
            Мы уже упоминали  возможности  канала  управления  устройством
         IOCTL.  Попросту говоря,  это средство пересылки буфера данных по
         вспомогательному каналу.  Механизм этого вызова идентичен  вызову
         функций ввода/вывода на основе ДОМД (функции 3FH и 40H),  за иск-
         лючением кодов  функций,  определяемых  содержимым  регистра  AX.
         Предназначены ли данные,  передаваемые по дополнительному каналу,
         для устройства или для самого драйвера - это дело разработчика.
            Не будьте, однако, ослеплены простотой этой функции и не восп-
         ринимайте ее как всего-лишь еще одну функцию ввода/вывода.  В со-
         ответствующем  приложении,  IOCTL может блестяще выступать в роли
         вторичного канала для взаимодействия с драйвером. Фирма Microsoft
         обеспечила  "запасную  дверь" для решения непредвиденных проблем.
         Они говорят - "Вам кажется,  что наш интерфейс с драйверами слиш-
         ком ограничен ? Должен быть более гибким ? Что же, попробуйте вот
         это." Такой подход является огромным шагом вперед по  сравнению с
         подходом "У нас нет этого, значит Вам это не нужно !", который не
         так давно был весьма распространен в среде  разработчиков систем.


                                      - 6-13 -
                                                         Таблица 6-1
                     Функции управления вводом/выводом (IOCTL)
         ДДДДДДВДДДДДДДДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
           Код ¦ Версия ¦Примечание¦           Назначение
          (AL=)¦ MS-DOS ¦          ¦
         ДДДДДД†ДДДДДДДД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
           0:  ¦  2.0+  ¦ #1,#2    ¦Получить информацию устройства
           1:  ¦  2.0+  ¦          ¦Установить информацию устройства
           2:  ¦  2.0+  ¦ #3,#6    ¦Читать из управляющего канала СУ
           3:  ¦  2.0+  ¦ #3,#6    ¦Писать в управляющий канал СУ
           4:  ¦  2.0+  ¦ #3,#7    ¦Читать из управляющего канала БУ
           5:  ¦  2.0+  ¦ #3,#7    ¦Писать в управляющий канал БУ
           6:  ¦  2.0+  ¦ #1       ¦Получить входную информацию
           7:  ¦  2.0+  ¦ #1       ¦Получить выходную информацию
           8:  ¦  3.0+  ¦ #2       ¦БУ поддерживает смену носителя ?
           9:  ¦  3.2+  ¦          ¦БУ локальное или удаленное ?
           A:  ¦  3.2+  ¦          ¦Описатель локальный или удаленный ?
           B:  ¦  3.0+  ¦ #4       ¦Изменить счетчик попыток
           C:  ¦  3.3+  ¦ #5       ¦Запрос на переключение кодовых страниц
           D:  ¦  3.3+  ¦ #5       ¦Запрос IOCTL для блоковых устройств
           E:  ¦  3.3+  ¦ #5       ¦Получить имя логического диска
           F:  ¦  3.3+  ¦ #5       ¦Установить имя логического диска
         ДДДДДДБДДДДДДДДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Примечание #1: Функция поддерживает как файлы, так и устройства.
         Примечание #2: Функция не поддерживает сетевую работу.
         Примечание #3: Функция разрешается  битом  14  словом  атрибутов
                        драйвера  и  поддержка определяется битом 14 слова
                        конфигурации.
         Примечание #4: Функция требует загрузки команды SHARE.
         Примечание #5: Функция разрешена битом 6 слова атрибутов драйвера.
         Примечание #6: СУ - символьное устройство.
         Примечание #7: БУ - блоковое устройство.

              Конфигурация с помощью команд управления вводом/выводом

            MS-DOS обеспечивает  выполнение команд конфигурации ("Получить
         или   Установить    информацию    устройства"),    поддерживаемых
         IOCTL-функцией. На рисунке 6-2 показано 16-битовое слово конфигу-
         рации, используемое функциями "Получить/Установить информацию ус-
         тройства" (коды 0 и 1). В текущих версиях MS-DOS могут быть опре-
         делены только младшие 8 бит этого слова.  Ниже описано назначение
         тех битов слова конфигурации,  которые имеют значение для драйве-
         ров устройств или влияют на способ обработки драйвером данных.

                                IOCTL БИТ 14: CTRL

            Бит CTRL устанавливается в 1 если драйвер  может  обрабатывать
         управляющие последовательности. Этот бит точно отражает состояние
         IOCTL бита в слове атрибутов драйвера устройства.  IOCTL-бит  ис-
         пользуется драйвером для оповещения MS-DOS о том, что драйвер бу-
         дет принимать управляющие последовательности.  Этот бит  применим
         как к файлам, так и к устройствам.



                                      - 6-14 -
          15  14  13  12  11  10  9   8   7   6   5   4   3   2   1   0
        ЙНННСНННСНННСНННСНННСНННСНННСНННСНННСНННСНННСНННСНННСНННСНННСННН»
        є R ¦ C ¦   ¦   ¦   ¦   ¦   ¦   ¦ I ¦ E ¦ B ¦ S ¦ I ¦ I ¦ I ¦ I є
        є E ¦ T ¦   ¦   ¦   ¦   ¦   ¦   ¦ S ¦ O ¦ I ¦ P ¦ S ¦ S ¦ S ¦ S є
        є S ¦ R ¦    R E S E R V E D    ¦ D ¦ F ¦ N ¦ E ¦ C ¦ N ¦ C ¦ C є
        є   ¦ L ¦   ¦   ¦   ¦   ¦   ¦   ¦ E ¦   ¦   ¦ C ¦ L ¦ U ¦ O ¦ I є
        є   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦ V ¦   ¦   ¦ L ¦ K ¦ L ¦ T ¦ N є
        ИНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННј
         ЗНАЧЕНИЯ БИТОВ                     УСТРОЙСТВО
         CTRL =1 : Поддержка управляющего   EOF  =0 : Конец файла на входе
                   канала                   BIN  =1 : Работа в двоичном
         ISDEV=1 : Канал - это устройство             режиме
              =0 : Канал - это файл         SPECL=1 : Специальное устрой-
                                                      ство
                                            ISCLK=1 : Устройство "ЧАСЫ"
         ФАЙЛ                               ISNUL=1 : Устройство NUL
         После записи в канал биты с 0 по   ISCOT=1 : Консоль вывода
         5 - это номер блокового устр-ва    ISCIN=1 : Консоль ввода

                    Рисунок 6-2. Слово конфигурации устройства.

                                IOCTL БИТ 7: ISDEV

            Бит ISDEV равен 1 если канал (или описатель)  открыт  к  уст-
         ройству. Если канал открыт к файлу, то этот бит сбрасывается в 0.

                                 IOCTL БИТ 5: BIN

            Пятый бит конфигурации (BIN) определяет подготовленный или не-
         подготовленный режим работы драйвера.  Другими словами,  этот бит
         определяет будут ли данные проходить дополнительную обработку при
         передаче или MS-DOS будет просто передавать "сырую" двоичную  ин-
         формацию между устройством и прикладной программой.  Под дополни-
         тельной обработкой подразумевается обработка определенных  управ-
         ляющих  символов,  расширение  символов  табуляции,  проверка  на
         нажатие клавиш CTRL-BREAK и т.п.
            Более традиционными  для этих функций являются термины "двоич-
         ный режим" и "ASCII режим",  соответствующие неподготовленному  и
         подготовленному  режимам.  В  руководстве программиста для MS-DOS
         приводятся более детальные инструкции о том,  как проверить и ус-
         тановить пятый бит. Мы, в свою очередь, обсудим влияние этого би-
         та на работу символьных драйверов.  (Заметьте,  что, как показано
         на рис.6-2, этот бит не используется для блоковых драйверов).
            Если символьный драйвер находится в подготовленном  режиме (по
         умолчанию), данные передаются побайтно. Другими словами, одно об-
         ращение к драйверу приводит к передаче одного символа. Это проис-
         ходит вне зависимости от того,  какое количество байт затребовано
         прикладной программой при  обращении  к  MS-DOS.  Например,  если
         прикладной программе требуется вывести 128 байт на символьное ус-
         тройство,  а драйвер работает в подготовленном режиме,  то MS-DOS
         сделает  128 обращений к драйверу с функцией "ВЫВОД" или "ВЫВОД С
         ПРОВЕРКОЙ", передавая за один вызов один байт.
            Посимвольного ввода/вывода можно избежать,  переведя драйвер в
         неподготовленный режим.  Последний может быть установлен только с
         помощью IOCTL функции. В неподготовленном режиме количество пере-
         даваемых байт, заданное прикладной программой, используется также
         при обращении к драйверу.  Пользуясь тем же самым примером,  если
         прикладная программа требует вывода 128 байт на  символьное  уст-

                                      - 6-15 -
         ройство,  и драйвер работает в неподготовленном режиме, то MS-DOS
         сделает единственное обращение к драйверу с функцией  "ВЫВОД" или
         "ВЫВОД С ПРОВЕРКОЙ",  задавая количество передаваемых байт равным
         128.
                                IOCTL БИТ 4: SPECL
            Подобно биту CTRL,  бит SPECL в слове конфигурации точно отра-
         жает  состояние бита SPECL в слове атрибутов.  Будучи установлен-
         ным,  этот бит означает, что данный драйвер (который почти всегда
         является  драйвером  консоли) способен выполнять высокоскоростной
         вывод в двоичном режиме, используя прерывание INT 29H.
            Бит BIN,  определяющий  неподготовленный режим,  также требует
         разрешения высокоскоростного режима вывода, определяемого атрибу-
         том SPECL. Если установлены как бит слова конфигурации BIN, так и
         бит слова атрибутов SPECL, значит драйвер будет работать в режиме
         высокоскоростного вывода. Этот режим и бит атрибутов SPECL обсуж-
         даются более глубоко в разделе "Слове атрибутов заголовка драйве-
         ра".

                      Группа команд управления вводом/выводом

            Четыре IOCTL команды, появившиеся в MS-DOS версии 3.3 - коман-
         ды C,  D, E и F - являются необязательными и разрешены только при
         установленном бите 6 слова атрибутов драйвера. Группа подфункций,
         обеспечиваемых командами C и D, представляет собой довольно "раз-
         ношерстное"  собрание  весьма специфических функций.  Эта группа,
         как правило,  используется для поддержки  определенных,  заданных
         изготовителем  устройства функциональных особенностей,  таких как
         переключение фонтов в принтере,  форматирование диска и т.д. Если
         Вам кажется, что у Вас есть необходимость использовать эти коман-
         ды,  следует обратиться к руководству программиста, где представ-
         лена более подробная информация.
            Команды E и F  позволяют  прикладному  программисту  управлять
         назначением и освобождением логических дисков,  например так, как
         это делается командой SUBST. Команда E ("Получить имя логического
         диска") возвращает назначение, использованное при последнем обра-
         щении к реальному устройству,  а команда F ("Установить имя логи-
         ческого диска") используется для изменения назначенных имен логи-
         ческих дисков.

             Прямой доступ к диску через прерывания INT 25H и INT 25H

            С другой  стороны  спектра  от  доступа к устройству с помощью
         описателей файлов (ДОМД) лежат прерывания прямого доступа к диску
         :  "Чтение по абсолютному адресу" (INT 25H) и "Запись по абсолют-
         ному адресу" (INT 26H).  Согласно  названию,  прерывания  прямого
         доступа  к диску работают исключительно с блоковыми устройствами,
         например с дисками.  Задача этих прерываний заключается в обеспе-
         чении работы с дисками напрямую,  не используя файловую структуру
         MS-DOS. Это может быть полезно в двух случаях.
            В первом случае,  программисты могут считывать или  записывать
         отдельные  части  стандартного диска MS-DOS,  содержащие файл или
         структуру директория. Это часто требуется, когда часть диска ста-
         новится плохой и невозможно,  поэтому, использовать FCB-метод или
         ДОМД.  В этом случае можно использовать функции прямого доступа к
         диску  для  того,  чтобы попробовать восстановить все,  что может
         быть восстановлено. Кроме того, программы могут считывать и запи-
         сывать  таблицу распределения (FAT) или директории диска,  недос-
         тупные  другим  методам.  Такая способность  требуется  утилитам,

                                      - 6-16 -
         сортирующим директории, изменяющим атрибуты файлов и т.п.
            Во втором случае,  использование этих функций  может  потребо-
         ваться в случае, если диск вообще не содержит таблицы распределе-
         ния файлов или директориев.  Такой диск  может  быть  использован
         только  как диск данных.  Такая же ситуация может встретиться при
         чтении дисков,  записанных в другой операционной среде, такой как
         CP/M  или  UCSD-p система.  Во всех этих случаях параметры диска,
         возвращаемые системе драйвером, делают невозможным доступ к диску
         любым другим методом.  Любая попытка выполнения файловых операций
         ввода/вывода,  включая чтение директория, возвратит мусор или со-
         общение об ошибке ("Non-DOS Disk"). Если Вы желаете получить под-
         робную информацию о том,  как  MS-DOS  определяет  формат  диска,
         просмотрите  описание команды драйверу "Построить блок параметров
         BIOS" в руководстве программиста или ином, аналогичном документе.
            Возвращаясь к обеспечению прямого доступа к диску, следует за-
         метить, что INT 25H и INT26H не выполняют блокирование и деблоки-
         рование  данных.  Блокирование и деблокирование требуются,  когда
         размер физического сектора на диске отличается от  размера  логи-
         ческой  записи,  используемого системой.  При блокировании данных
         система собирает вместе достаточное количество записей для запол-
         нения физического сектора перед сохранением его на диске.  Дебло-
         кирование используется при чтении с диска,  т.к.  один физический
         сектор может содержать несколько записей. В последнем случае сис-
         тема считывает целый сектор и,  затем,  выбирает оттуда требуемые
         программе  записи.  Функции доступа к диску по абсолютным адресам
         считывают и записывают только целые секторы,  так что программист
         обязан знать размер сектора диска для того,  чтобы определить ко-
         личество считанных или записанных байтов.
            В связи с тем, что параметры, используемые этими прерываниями,
         передаются драйверу без какого-либо преобразования, операции чте-
         ния и записи передают блоки данных размером, кратным длине секто-
         ра диска.  Это отличает данный метод доступа  от  FCB-метода  или
         описатель-ориентированного метода доступа,  где ввод/вывод  опре-
         деляется в терминах логических блоков и записей,  а  MS-DOS  осу-
         ществляет  преобразование логических блоков в физические секторы.
            Последняя особенность функций прямого доступа к диску заключа-
         ется в том,  что они возвращаются из прерываний INT 25H и INT 26H
         при помощи команды RETF,  а не IRET,  оставляя при этом флаги  на
         стеке.  Поэтому,  после проверки корректности выполнения функции,
         Вы должны убрать флаги со стека.

                          Опция "Ввод/вывод с проверкой"

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

                1.С командной строки MS-DOS пользователь  может  выполнить
                команды "VERIFY ON" или "VERIFY OFF" для того, чтобы соот-
                ветственно включить или выключить эту опцию.

                2.Для некоторых команд MS-DOS,  таких как COPY,  можно за-
                дать ключ /V, который включает опцию проверки на время вы-
                полнения команды.

                                      - 6-17 -
                3.Опция проверки может быть  включена  и  выключена  любой
                программой,  используя  функцию  MS-DOS  2H ("Включить или
                выключить опцию проверки").

                                      Выводы

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

                           Создание драйверов устройств

            Создание драйверов в любой операционной  системе  имеет  много
         преимуществ перед написанием обычных программ. Драйверы устройств
         должны следовать строго определенной структуре,  а если структура
         понятна, то остальное приложится.
            Базовая структура  драйвера  устройства  показана  на рис.6-3.
         Обязательно должны присутствовать три раздела драйвера -- ЗАГОЛО-
         ВОК ДРАЙВЕРА,  ПРОГРАММА СТРАТЕГИЙ и ПРОГРАММА ПРЕРЫВАНИЙ.  Прог-
         рамма ПРЕРЫВАНИЙ это не тоже самое,  что программа обработки пре-
         рываний,  которая  может присутствовать в качестве необязательной
         части работающего по прерываниям драйвера.  На самом деле,  прог-
         рамма ПРЕРЫВАНИЙ - это точка входа в драйвер для обработки  полу-
         чаемых от MS-DOS команд.


                               ЪДДДДДДДДДДДДДДДДДДДДДДДДД·
                               ¦   Заголовок драйвера    є
                               ГДДДДДДДДДДДДДДДДДДДДДДДДД¶
                               ¦ Область данных драйвера є
                               ГДДДДДДДДДДДДДДДДДДДДДДДДД¶
                               ¦   Программа СТРАТЕГИЙ   є
                               ГДДДДДДДДДДДДДДДДДДДДДДДДД¶
                               ¦         Вход в          є
                               ¦   программу ПРЕРЫВАНИЙ  є
                               ГДДДДДДДДДДДДДДДДДДДДДДДДД¶
                               ¦    Обработчик команд    є
                               ГДДДДДДДДДДДДДДДДДДДДДДДДД¶
                               ¦   Программа обработки   є
                               ¦       прерываний        є
                               ГДДДДДДДДДДДДДДДДДДДДДДДДД¶
                               ¦ Процедура инициализации є
                               ФНННННННННННННННННННННННННј

                     Рисунок 6-3. Структура драйвера в MS-DOS

                                      - 6-18 -
            В программе  6-1 представлен скелет драйвера устройства.  Хотя
         структура драйвера похожа на структуру .COM программы,  важно от-
         метить следующие отличия :

          1. Программа начинается с нулевого смещения, а не 100H.

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

          3. Программа  не  содержит  директивы  ASSUME  для стекового
             сегмента.

          4. Программа не содержит директивы END START.


         Листинг 6-1. Заголовок драйвера, программы СТРАТЕГИЙ и ПРЕРЫВАНИЙ
        ------------------------------------------------------------------

    DRIVER    SEGMENT PARA
              ASSUME  CS:DRIVER,DS:NOTHING,ES:NOTHING
              ORG     0
    START     EQU     $                       ; Начало драйвера
    ;
    ;******* ЗАГОЛОВОК ДРАЙВЕРА *******************************************
    ;
              dw      -1,-1            ; Указатель на следующий драйвер
              dw      ATTRIBUTE        ; Слово атрибутов
              dw      offset STRATEGY  ; Точка входа в программу STRATEGY
              dw      offset INTERRUPT ; Точка входа в программу INTERRUPT
              db      8 dup (?)        ; Количество устройств/поле имени
    ;
    ;******* РЕЗИДЕНТНАЯ ЧАСТЬ ДРАЙВЕРА ***********************************
    ;
    req_ptr   dd      ?                ; Указатель на заголовок запроса
       .
       .
       .
    ;
    ;******* ПРОГРАММА СТРАТЕГИИ ******************************************
    ;
    ; Сохранить адрес заголовка запроса для программы СТРАТЕГИЙ в REQ_PTR.
    ; На входе адрес заголовка запроса находится в регистрах ES:BX.
    ;
    STRATEGY  PROC    far
              mov     cs:word ptr [req_ptr],bx
              mov     cs:word ptr [req_ptr + 2],bx
              ret
    STRATEGY  endp
    ;
    ;******* ПРОГРАММА ПРЕРЫВАНИЙ *****************************************
    ;
    ; Обработать команду, находящуюся в заголовке запроса. Адрес заголовка
    ; запроса содержится в REQ_PTR в форме СМЕЩЕНИЕ:СЕГМЕНТ.
    ;
    INTERRUPT  PROC    far
              pusha                    ; Сохранить все регистры

                                      - 6-19 -
               lds     bx,cs:[req_ptr]  ; Получить адрес заголовка запроса
                .
                .
                .
     INTERRUPT  ENDP
                .
                .
                .
     DRIVER    ENDS
               END

     ---------------------------------------------------------------------

                                Заголовок драйвера

            Заголовок драйвера -- это блок данных длиной 18  байт, которым
         должен начинаться любой драйвер. Заголовок драйвера всегда должен
         располагаться начиная с нулевого смещения  в  сегменте  драйвера.
         При  загрузке драйвера MS-DOS считывает заголовок для того, чтобы
         определить тип драйвера и точки  входа  в  драйвер.  В  заголовке
         драйвера содержится четыре типа сведений, критичных для использо-
         вания драйвера системой :  ПОЛЕ СВЯЗИ,  СЛОВО АТРИБУТОВ,  ВЕКТОРА
         ТОЧЕК ВХОДА и ПОЛЕ ИМЕНИ/КОЛИЧЕСТВА.

                                    Поле связи

            Первые четыре байта заголовка драйвера это FAR указатель (сме-
         щение:сегмент) на следующий драйвер в цепочке (списке) драйверов.
         При создании драйвера эти байты устанавливаются равными FFFF:FFFF
         (-1).  При  загрузке  нового драйвера его адрес помещается в поле
         связи предыдущего драйвера.  Исключением являются файлы, содержа-
         щие  несколько  драйверов одновременно.  В этом случае первые два
         байта поля связи должны содержать смещение  заголовка  следующего
         драйвера.

                                  Слово атрибутов

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

                Драйвер диска формата IBM - 0000
                Стандартный драйвер консоли - 8003H
                Драйвер стандартного устройства (напр. PRN) - 8000H

         БИТ15: CHR.  Бит CHR должен быть сброшен в 0,  если драйвер пред-
         назначен  для  блоковых устройств,  и должен быть установлен в 1,
         если драйвер будет обслуживать символьное устройство  (см. раздел
         "Типы драйверов устройств").

         БИТ14: IOCTL.  Бит  IOCTL является необязательным.  Его установка
         информирует MS-DOS о том, что драйвер поддерживает средства кана-
         ла прямого управления. Если IOCTL бит установлен, то драйвер ОБЯ-
         ЗАН поддерживать команды 3 и 12 (IOCTL ввод и вывод), в противном

                                      - 6-20 -
         случае бит 14 должен быть сброшен. Указанные команды доступны при
         помощи подфункций 2 и 3 (для символьных устройств) или 4 и 5 (для
         блоковых устройств) функции MS-DOS 44H.
         БИТ13: NONIBM/OTB.  Для  блоковых  драйверов  этот бит называется
         также NONFAT бит. Будучи установленным, этот бит указывает на то,
         что блоковое устройство может не поддерживать стандартной для IBM
         /MS-DOS структуры диска. В этом случае обработка драйвером команд
         INIT и BUILD BPB будет происходить особым образом. Для символьных
         драйверов в MS-DOS версий 3.2 и более  поздних,  этот  бит  носит
         название  OTB  (Output  Until Busy) -- "Вывод пока не занято",  и
         указывает на то,  что драйвер поддерживает дополнительную команду
         9  (Output  Until Busy).  Эта команда полезна для символьных уст-
         ройств,  имеющих буфер большой емкости, таких как некоторые прин-
         теры. Символьные драйверы, используемые с MS-DOS версий 3.1 и ни-
         же, должны иметь этот бит сброшенным в 0.

         БИТ12: NETWORK. Этот бит является необязательным атрибутом, впер-
         вые определенный в MS-DOS версии 3.10. Интересно, что бит NETWORK
         не упоминался в последующей документации по MS-DOS версий 3.2 или
         3.3,  так что использование его в настоящее время оставляет неко-
         торые вопросы.  Это бит предназначен для информирования MS-DOS  о
         том, что драйвер обслуживает сетевое устройство. Сетевые устройс-
         тва помечаются как блоковые устройства  в  слове  атрибутов;  при
         этом делается допущение, что обслуживаемое сетевое устройство яв-
         ляется "окном" в сеть,  позволяя, таким образом, целиком перенап-
         равлять на обработку удаленному устройству системные вызовы.  Ко-
         нечно,  для  того,  чтобы  воспользоваться  услугами   сети   для
         указанного перенаправления, необходима поддержка соответствующего
         средства, такого как MS-NET.

         БИТ11: OCRM. Атрибут OCRM (Open/Close/Removable Media) появляется
         начиная с MS-DOS версии 3.0. Он может использоваться как для сим-
         вольных,  так и для блоковых драйверов. Этот бит является не обя-
         зательным,  хотя Microsoft рекомендует устанавливать его для всех
         новых драйверов. Поняв назначение этого атрибута, программист не-
         сомненно сможет определиться в его использовании (или не  исполь-
         зовании).

            Как для символьных,  так и для  блоковых  драйверов  установка
         этого  бита  означает  поддержку  драйвером  команд DEVICE OPEN и
         DEVICE CLOSE (команды 13 "Открыть устройство" и 14  "Закрыть  уст-
         ройство").  Блоковые  драйверы  с установленным битом OCRM должны
         также поддерживать команду CHECK FOR REMOVABLE MEDIA (команда 15,
         "Проверка замены носителя").
            Для блоковых устройств команды DEVICE OPEN и DEVICE CLOSE  вы-
         даются  только  в  режиме  совместного использования файлов (file
         sharing).  Этот режим включается после запуска команды SHARE.EXE.
         При установленном режиме совместного использования файлов, коман-
         да DEVICE OPEN выдается драйверу при вызове  функций  MS-DOS  0FH
         ("Открыть файл, используя FCB") или 3DH ("Открыть файл при помощи
         вызова  функций 10H ("Закрыть файл, используя FCB") или 3H ("Зак-
         рыть файл при помощи описателя").  Для  дисковых устройств коман-
         ды  DEVICE  OPEN  и  DEVICE CLOSE можно использовать для подсчета
         числа открытий определенного устройства, например, числа открытых
         файлов на диске.  Это может быть полезно при определении недопус-
         тимости смены дискеты в дисководе, если на момент замены носителя

                                      - 6-21 -
         оставались открытые файлы.
            Для символьных устройств команды DEVICE OPEN  и  DEVICE  CLOSE
         выдаются  всегда,  когда соответствующее устройство открывается и
         закрывается,  независимо от режима совместного использования фай-
         лов,  так что загрузка команды SHARE.EXE не требуется. При работе
         с устройствами могут быть использованы только функции  MS-DOS 3DH
         ("Открыть файл при помощи описателя") и 3H ("Закрыть файл при по-
         мощи описателя"),  так как FCB-метод не работает с  устройствами.
         Для символьных устройств команды DEVICE OPEN и DEVICE CLOSE могут
         быть использованы для предотвращения одновременного доступа к та-
         ким устройствам, как принтер или модем, а также для вызова проце-
         дур пред- и после обработки,  таких как процедуры настройки прин-
         тера или завершение сеанса связи для модема.
            Заметим, что устройства CON,  AUX и PRN открыты всегда, так как
         связаны с описателями 0,  1,  и 2 (STDIN,  STDOUT и STDERR --  все
         отображаются на устройство CON),  описателем 3 (STDAUX, отображае-
         мый на устройство AUX) и описателем  4  (STDPRN,  отображаемый  на
         устройство PRN).
            Команда CHECK  FOR REMOVABLE MEDIA выдается при вызове пользо-
         вателем функции MS-DOS 44H  ("Управление  работой  устройств")  с
         подкомандой номер 8.  Драйвер должен вернуть информацию о наличии
         сменного либо несменного носителя.
            Атрибут OCRM  (Open/Close/Removable  Media)  также учитывается
         при обработке драйвером команды BUILD BPB ("Построить блок  пара-
         метров  BIOS").  Сменный  носитель может содержать "идентификатор
         тома", одиннадцатисимвольное имя диска. Если устройство поддержи-
         вает сменный носитель,  имя тома должно быть определено и обрабо-
         тано драйвером. Подробнее об этом можно найти при описании коман-
         ды BUILD BIOS PARAMETER BLOCK.

         БИТЫ с 10 по 7 : Зарезервированы.
         ---------------------------------
         БИТ6: GIOCTL.  В MS-DOS версии 3.3 бит GIOCTL ("Группа команд уп-
         равления") устанавливается в 1 для индикации того,  что  блоковый
         или символьный драйвер поддерживает дополнительные подкоманды ко-
         мандой 19 (GENERIC I/O CONTROL REQUEST).  Если этот бит разрешает
         использование  команды 19,  драйвер должен также поддерживать ко-
         манды 23 и 24 (GET/SET LOGICAL DRIVE --  Получить/Установить  имя
         логического диска).
            При поддержке драйвером указанных команд,  программа пользова-
         теля  может  выдать команду GENERIC I/O CONTROL REQUEST с помощью
         функции 44H MS-DOS (подфункции 0CH и 0DH). Для блоковых драйверов
         команды  GET/SET  LOGICAL DRIVE могут быть выполнены вызовом под-
         функций 0H (GET LOGICAL DRIVE) и 0FH (SET LOGICAL  DRIVE) функции
         MS-DOS 44H. Для получения более подробной информации обратитесь к
         описанию функции 44H и описанию команд драйвера GENERIC  IOCTL  и
         GET/SET LOGICAL DRIVE.

         БИТ 5 : Зарезервирован.
         -----------------------

         БИТ4: SPECL. Бит SPECL является необязательным атрибутом, исполь-
         зуемым  только драйвером консоли,  и информирующим MS -DOS о том,
         что драйвер установил специальный обработчик INT 29H для выполне-
         ния  высокоскоростного  вывода на консоль (устройство CON).  Если
         этот бит установлен, то при необходимости быстрого вывода на кон-

                                      - 6-22 -
         соль MS-DOS выдает программное прерывание INT 29H,  передавая вы-
         водимый символ в регистр AL.  Режим быстрого вывода управляется и
         индицируется  битом 5 (режим двоичного вывода) в слове конфигура-
         ции. При выдаче прерывания INT 29H ожидается, что драйвер выведет
         переданный в регистре AL символ и вернет управление. Обычные про-
         цедуры ввода/вывода пропускаются. Как стандартный драйвер консоли
         MS-DOS,  так  и  заменяющий его драйвер ANSI.SYS поддерживают эту
         особенность.  Если используемый драйвер консоли поддерживает пре-
         рывание  INT  29H  (что  определяется  чтением слова конфигурации
         драйвера), то прикладная программа также может осуществлять быст-
         рый вывод на консоль, используя INT 29H.
            Заметим, что этот бит объявлен резервным в документации  IBM и
         вообще игнорируется в последней документации фирмы Microsoft. Оба
         этих факта говорят о том,  что поддержка бита SPECL в будущем  не
         гарантируется.

         БИТ3: CLOCK.  Бит  CLOCK  устанавливается на драйвере символьного
         устройства, имеющего имя "CLOCK$", для обозначения этого устройс-
         тва,  как устройства системного времени. Так как драйвер устройс-
         тва "Часы" практически всегда обеспечивается системой,  необходи-
         мость использования этого бита возникает довольно редко.
            Драйвер устройства "Часы" обычно  является  обычным  драйвером
         символьного  устройства  без  каких-либо дополнительных атрибутов
         (слово атрибутов 8008H).  Время считывается командой INPUT (ввод)
         и  устанавливается командой OUTPUT (вывод).  По любой из этих ко-
         манд всегда передается ровно 6 байт, имеющих следующее значение :

                # БАЙТА         РАЗМЕР        ЗНАЧЕНИЕ

                0, 1            16 бит        Количество дней с 1.1.1980г.
                2               8 бит         Минуты
                3               8 бит         Часы
                4               8 бит         Сотые доли секунды
                5               8 бит         Секунды

         БИТ2: NUL.  Бит NUL означает, что данный драйвер является драйве-
         ром устройства NUL. В связи с тем,  что NUL-драйвер не может быть
         заменен,  нет  никакой необходимости создавать драйвер устройства
         NUL.

         БИТЫ1и0: STDIN и STDOUT. Биты STDIN и STDOUT означают, что данный
         драйвер является соответственно драйвером стандартного устройства
         ввода и вывода. Для устройства CON, обслуживающего системную кла-
         виатуру и монитор, эти биты почти всегда определяются вместе. Ес-
         ли устанавливается новый драйвер консоли (такой как ANSI.SYS) для
         того,  чтобы добавить какие-либо новые возможности,  то оба  этих
         бита  должны  быть установленными.  Атрибуты STDIN и STDOUT могут
         быть установлены только на одном драйвере из всех активных  (дру-
         гие копии CON-драйвера тоже могут иметь эти атрибуты,  однако ак-
         тивным будет только последний установленный CON-драйвер).

                Вектора точек входа программ СТРАТЕГИЙ и ПРЕРЫВАНИЙ

            Следующие два слова заголовка драйвера содержат смещения прог-
         рамм  СТРАТЕГИЙ и ПРЕРЫВАНИЙ,  соответственно.  MS-DOS использует
         эту информацию совместно с сегментным адресом драйвера для  опре-
         деления точек входа в указанные программы. Сегментный адрес драй-
         вера система, конечно же, узнает при его загрузке.


                                      - 6-23 -
                          Поле имени/количества устройств

            Последние восемь байт заголовка драйвера  служат  двум  целям.
         Для  символьных драйверов это поле содержит ASCII имя устройства,
         дополненных справа пробелами. Например, для драйвера принтера это
         поле может содержать строку 'PRN '.
            Для блоковых устройств имеет значение только первый  байт.  Он
         показывает MS-DOS сколько отдельных устройств поддерживается дан-
         ным драйвером.  Такая возможность необходима потому,  что  многие
         контроллеры  поддерживают более одного физического дисковода. Так
         как остальные семь байт поля в этом случае не  используются,  там
         можно хранить имя устройства для поиска драйвера в памяти или для
         идентификации драйвера. Например, поле количества устройств драй-
         вера RAM-диска,  называемого RDISK (см. листинг 6-10), может быть
         определено как :

                UNIT_FIELD      DB       1, 'RDISK   '

                                Программа СТРАТЕГИЙ

            Следующий раздел  драйвера устройства  - это программа СТРАТЕ-
         ГИЙ.  В листинге 6-1 она занимает только три  строки  выполняемых
         кодов.  Единственное назначение программы СТРАТЕГИЙ заключается в
         сохранении адреса блока запроса для последующего его  использова-
         ния программой ПРЕРЫВАНИЙ.
            Что представляет собой блок запроса? Листинг  6-2 представляет
         структуру заголовка запроса. С него начинается любой блок запроса
         ввода/вывода к драйверу.  Для блока запроса может  иногда  требо-
         ваться  больше информации,  чем содержится в в заголовке запроса,
         поэтому заголовок содержит поле  длины  информации.  К  заголовку
         запроса мы еще вернемся,  а сейчас продолжим обсуждение программы
         СТРАТЕГИЙ.
                     Листинг 6-2. Структура заголовка запроса
         -----------------------------------------------------------------

         request     equ        ds:[bx]     ; базовый адрес заголовка
         reqhdr      struc                  ;       запроса.
         length      db         ?           ; длина блока запроса (байт).
         unit        db         ?           ; количество устройств.
         command     db         ?           ; код команды для драйвера.
         status      dw         ?           ; возвращаемое состояние.
                     db         8 dup (?)   ; резерв.
         reqhdr      ends

         -----------------------------------------------------------------

            Причина того,  что  программа СТРАТЕГИЙ обязана сохранять адрес
         заголовка запроса заключается в  том,  что  MS-DOS  выполняет  не
         единственное обращение к драйверу для выполнения определенной ко-
         манды. На самом деле,  система сначала делает предварительное об-
         ращение к драйверу для того,  чтобы информировать драйвер о  том,
         что  он должен сделать и затем делает повторное обращение для вы-
         полнения требуемых действий.
            Такое двухэтапное обращение к драйверу имеет смысл  при работе
         в MS-DOS какой-либо многозадачной системы.  В этом случае запросы
         к драйверу от разных задач могут выдаваться в любой момент време-
         ни.  Путем  выделения  в  драйвере самостоятельных частей анализа

                                      - 6-24 -
         запроса и выполнения запроса драйвер  может  принимать  множество
         запросов, одновременно удовлетворяя полученный ранее запрос.
            MS-DOS передает  программе СТРАТЕГИЙ адрес блока запроса в ре-
         гистрах ES:BX. Хотя программа СТРАТЕГИЙ должна сохранять сам блок
         запроса, большинство драйверов ограничивается сохранением его ад-
         реса. Это возможно из-за того, что MS-DOS в настоящее время вызы-
         вает программу ПРЕРЫВАНИЙ непосредственно после возврата управле-
         ния  от  программы  СТРАТЕГИЙ,  не  изменяя  информации  в  блоке
         запроса.  Следующий  пример демонстрирует фрагмент кода,  который
         сохраняет блок запроса, используя описанную методику :

                mov      cs:word ptr [req_ptr],bx
                mov      cs:word ptr [req_ptr + 2],es

            Однако, как  только  MS-DOS  станет многозадачной,  сохранение
         только указателя на блок запроса будет уже  недопустимо.  В  этом
         случае  программа  СТРАТЕГИЙ должна будет не только сохранять сам
         блок запроса но и,  возможно,  помещать блоки запросов в  очередь
         (если,  конечно, эту функцию не возьмет на себя MS-DOS). Впрочем,
         до тех пока этого не случилось, мы можем пользоваться более прос-
         тым способом сохранения адреса блока.
            Как программа ПРЕРЫВАНИЙ так и программа СТРАТЕГИЙ должны быть
         определены  в  драйвере  как FAR процедуры,  возвращая управление
         MS-DOS, соответственно,  командой RETF. В связи с тем, что MS-DOS
         вызывает  эти подпрограммы с помощью команды CALL типа FAR, любая
         иная команда возврата приведет либо к передаче управления по  не-
         верному адресу (RETN) либо к порче стека (IRET).

                               Программа ПРЕРЫВАНИЙ

            После того,  как  программа  СТРАТЕГИЙ  сохраняет указатель на
         блок запроса и возвращает управление,  MS-DOS вызывает  программу
         ПРЕРЫВАНИЙ  (называемую также точкой входа запроса в документации
         фирмы IBM по PC DOS). Собственно запрос к драйверу обрабатывается
         именно этой программой.
            Самое первое действие, которое должна выполнить программа ПРЕ-
         РЫВАНИЙ - это сохранить все регистры. На момент обращения к драй-
         веру устройства стек имеет емкость примерно в 20 слов. Сохранение
         всех регистров,  включая флаги,  требует 14 слов.  Если программе
         ПРЕРЫВАНИЙ требуется для работы более чем 6 слов стека, она долж-
         на установить свой собственный локальный стек.
            После сохранения текущего состояния процессора, программа ПРЕ-
         РЫВАНИЙ  должна  получить  блок  запроса,  сохраненный программой
         СТРАТЕГИЙ.  Если адрес этого блока был сохранен с помощью  приве-
         денных выше команд,  то получить адрес блока параметров можно ко-
         мандой LDS

               lds        bx,cs:[req_ptr]  ; получить адрес блока запроса

            Теперь, получив доступ к заголовку блока запроса,  можно начи-
         нать его обработку.  Первый шаг заключается  в  анализе  запроса.
         Доступ  к  нужным полям блока запроса будет значительно облегчен,
         если описана структура заголовка. Структура, которую мы использу-
         ем  в драйвере RDISK и которая определяет формат заголовка запро-
         са, показана в листинге 6-2.
            Если драйвер должен обслуживать блоковое устройство, то первый
         элемент заголовка запроса, который должен быть проверен, это поле

                                      - 6-25 -
         количества устройств (request.unit).  После проверки корректности
         поля request.unit,  программа ПРЕРЫВАНИЙ должна получить из блока
         запроса код команды (request.command),  которую требуется  выпол-
         нить.  Символьные драйверы могут обращаться сразу к коду команды,
         т.к.  каждый символьный драйвер поддерживает только одно устройс-
         тво.
            Определив код команды,  программа ПРЕРЫВАНИЙ  должна  передать
         управление соответствующему обработчику. В листинге 6-3, содержа-
         щем пример программы ПРЕРЫВАНИЙ, показан один из способов переда-
         чи управления требуемому обработчику, основанный на использовании
         таблицы переходов.  Таблица переходов представляет собой последо-
         вательность  смещений программ-обработчиков команд.  Для передачи
         управления определенному обработчику  необходимо  указать  индекс
         требуемой подпрограммы,  заданной своим смещением в таблице пере-
         ходов. Этот индекс (в нашем случае это код команды) преобразуется
         в  смещение  в  таблице,  после  чего выполняется косвенный вызов
         подпрограммы или переход на нее через таблицу переходов

                call       word ptr cs:jumptab[bx]  ; обработать команду

            В связи с тем,  что индекс (т.е. код команды) может быть боль-
         ше, чем максимальный из используемых кодов команд, программа ПРЕ-
         РЫВАНИЙ должна выполнять проверку индекса для  того,  чтобы  убе-
         диться в его правильности.  При этом,  вместо сравнения индекса с
         каким-либо заранее фиксированным значением,  программа ПРЕРЫВАНИЙ
         сравнивает код команды с максимально допустимым значением, храня-
         щимся в поле max_cmd :

                cmp        bl,[max_cmd]             ; команда допустима ?

            Для того, чтобы понять пользу хранения максимально допустимого
         значения  в  памяти,  взгляните  на  таблицу 6-2.  В этой таблице
         представлены команды,  поддерживаемые различными версиями MS-DOS.
         Максимальный  код  команды,  обеспечиваемый MS-DOS версий до 3.0,
         имеет значение 0CH. Однако, учитывая тот факт, что max_cmd распо-
         лагается  в памяти,  драйвер может модифицировать это значение во
         время инициализации,  позволяя, таким образом, использовать новые
         команды,  если  драйвер  загружен  под  управлением  новой версии
         MS-DOS.

















         
                                      - 6-26 -
                                                         Таблица 6-2
                         Команды для драйверов устройств

    ДДДДДДДДВДДДДДДДВДДДДДДДДДВДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДД
    Команда ¦ Версия¦ Блоковые¦ Симв-ные¦ Атрибут ¦ Название команды
            ¦  DOS  ¦  устр-ва¦  устр-ва¦         ¦
    ДДДДДДДД†ДДДДДДД†ДДДДДДДДД†ДДДДДДДДД†ДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДД
      0:    ¦  2.0  ¦    +    ¦    +    ¦         ¦ INIT
      1:    ¦  2.0  ¦    +    ¦    -    ¦         ¦ MEDIA CHECK
      2:    ¦  2.0  ¦    +    ¦    -    ¦         ¦ BUILD BPB
      3:    ¦  2.0  ¦    +    ¦    +    ¦ 14:IOCTL¦ INPUT IOCTL
      4:    ¦  2.0  ¦    +    ¦    +    ¦         ¦ INPUT
      5:    ¦  2.0  ¦    -    ¦    +    ¦         ¦ Nondestructive INPUT
      6:    ¦  2.0  ¦    -    ¦    +    ¦         ¦ INPUT STATUS
      7:    ¦  2.0  ¦    -    ¦    +    ¦         ¦ INPUT FLUSH
      8:    ¦  2.0  ¦    +    ¦    +    ¦         ¦ OUTPUT
      9:    ¦  2.0  ¦    +    ¦    +    ¦         ¦ OUTPUT with VERIFY
     10:    ¦  2.0  ¦    -    ¦    +    ¦         ¦ OUTPUT STATUS
     11:    ¦  2.0  ¦    -    ¦    +    ¦         ¦ OUTPUT FLUSH
     12:    ¦  2.0  ¦    +    ¦    +    ¦ 14:IOCTL¦ OUTPUT IOCTL
     13:    ¦  3.0  ¦    +    ¦    +    ¦ 11:OCRM ¦ DEVICE OPEN
     14:    ¦  3.0  ¦    +    ¦    +    ¦ 11:OCRM ¦ DEVICE CLOSE
     15:    ¦  3.0  ¦    +    ¦    -    ¦ 11:OCRM ¦ REMOVABLE MEDIA
     16:    ¦  3.1  ¦    -    ¦    +    ¦ 13:OTB  ¦ OUTPUT until busy
     19:    ¦  3.2  ¦    +    ¦    +    ¦ 6:GIOCTL¦ Generic IOCTL Request
     23:    ¦  3.2  ¦    +    ¦    -    ¦ 6:GIOCTL¦ Get Logical Device
     24:    ¦  3.2  ¦    +    ¦    -    ¦ 6:GIOCTL¦ Set Logical Device
     ДДДДДДДБДДДДДДДБДДДДДДДДДБДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДД
      Примечание 1. В колонке "Версия DOS" указана самая ранняя версия
                    MS-DOS, начиная с которой поддерживается эта команда.
      Примечание 2. В колонке "Атрибут" указаны бит слова атрибутов
                    драйвера, разрешающий использование данной команды.
     ННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН

            Последняя задача программы ПРЕРЫВАНИЙ после обработки  требуе-
         мой команды заключается в установке статуса возврата в блоке зап-
         роса.  В листинге 6-3 ожидается,  что обработчик  каждой  команды
         возвращает статус завершения в регистре AX. После возврата управ-
         ления от обработчика,  программа ПРЕРЫВАНИЙ записывает  статус  в
         поле слова состояния заголовка запроса (поле request.status). За-
         тем программа ПРЕРЫВАНИЙ устанавливает бит DONE  ("выполнено")  в
         слове  состояния и возвращает управление MS-DOS.  Так как возврат
         управления должен быть выполнен командой RETF, программа ПРЕРЫВА-
         НИЙ определяется как процедура типа FAR.

                     Листинг 6-3. Пример программы ПРЕРЫВАНИЙ.
         -----------------------------------------------------------------
     ;
     ; Определение битов слова состояния драйвера устройства
     ;
     ST_ERROR    equ        1000000000000000b       ; была ошибка
     ST_BUSY     equ        0000001000000000b       ; устройство занято
     ST_DONE     equ        0000000100000000b       ; команда выполнена
     ;
     ; Определение кодов ошибки при обработке команд
     ;
     WRITE_PROTECT          equ      0              ; защита от записи
     UNKNOWN_UNIT           equ      1              ; неопознано устройство
     NOT_READY              equ      2              ; устройство не готово

                                      - 6-27 -
     UNKNOWN_COMMAND        equ      3              ; команда не опознана
            .
            .
     ;
     ;********** Точка входа в программу ПРЕРЫВАНИЙ ************************
     ;
     INTERRUPT    proc       far
                 pusha                              ; сохраним все рабочие
                 push       ds                      ;   регистры
                 push       es
                 push       cs                      ; установим локальный
                 pop        ds                      ;   сегмент данных
                 les        di,[req_ptr]            ; получим адрес блока
                 mov        bl,es:[di.command]      ;   запроса и код
                                                    ;   команды
     ;
     ; Установим заранее флаг ошибки (на случай, если команда будет
     ;   неопознана)
     ;
                 mov        ax,(ST_ERROR or UNKNOWN_COMMAND)
                 cmp        bl,[max_cmd]             ; эта команда
                 ja         exit                     ;   поддерживается ?
     ;
     ; Передадим управление соответствующему обработчику.  На входе каждый
     ; обработчик получает регистры CS и DS установленными на  сегмент
     ; DRIVER  и регистры ES:DI указывающими на блок запроса.  Свой статус
     ; обработчик должен вернуть в регистре AX.
     ;
                 xor        bh,bh                    ; превратим команду
                 shl        bx,1                     ;   в индекс
                 call       word ptr cs:jumptab[bx]  ; обработаем команду
     ;
     ; Запишем статус в слово состояния блока запроса
     ;
     exit:       push       cs
                 pop        ds
                 les        di,[req_ptr]            ; получим адрес блока
                 or         ax,ST_DONE              ; запроса, установим
                 mov        es:[di.status],ax       ; бит DONE и сохраним
                 pop        es                      ; статус
                 pop        ds                      ; восстановим контекст
                 popa
                 ret                                ; RETF
     INTERRUPT    endp
            .
            .
            .
     ;
     ;********** Таблица переходов на обработку команд *******************
     ;
     JUMPTAB     label      word
                 dw         offset INIT            ; 0 - Инициализация
                 dw         offset MEDIA_CHECK     ; 1 - Проверка носителя
                 dw         offset BUILD_BPB       ; 2 - Построить BPB
                  .
                  .
                  .

                                      - 6-28 -
                 dw         offset NO_COMMAND      ; 16
                 dw         offset GET_LOGICAL     ; 17 - Получить имя ЛУ
                 dw         offset SET_LOGICAL     ; 18 - Установить ЛУ
                  .
                  .
                  .

     ---------------------------------------------------------------------

            Слово состояния, показанное на рис.6-5, используется для инди-
         кации ошибок,  случившихся при выполнении какой-либо команды (бит
         ERROR -- ошибка) и для отображения состояния устройства по коман-
         дам  опроса  статуса и проверки смены носителя (бит BUSY -- заня-
         то).

        15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
       ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД·
       ¦ E ¦   ¦   ¦   ¦   ¦   ¦ B ¦ D ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   є
       ¦ R ¦   ¦   ¦   ¦   ¦   ¦ U ¦ O ¦   ¦   ¦  КОД  ОШИБКИ  ¦   ¦   є
       ¦ R ¦  ЗАРЕЗЕРВИРОВАНО  ¦ S ¦ N ¦   ¦  ЕСЛИ БИТ 15 РАВЕН 1  ¦   є
       ¦ O ¦   ¦   ¦   ¦   ¦   ¦ Y ¦ E ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   є
       ¦ R ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   є
       ФНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННПНННј
         Значение битов :

            ERROR = 1 : При обработке команды случилась ошибка.
                        Код ошибки находится в битах с 0 по 7.
            BUSY  = 1 : Устанавливается командами опроса состояния
                        и проверки смены носителя.
            DONE  = 1 : Команда выполнена. Устанавливается на выходе.
                 Рисунок 6-5. Слово состояния драйвера устройства

            Бит ERROR устанавливается, если возникла ошибка при выполнении
         какой-либо  команды  или  если  команда является недопустимой для
         данного драйвера.  При установленном бите ошибки  драйвер  обязан
         поместить соответствующий код ошибки в биты с 0 по 7 слова состо-
         яния.  Возможные ошибки и их коды перечислены в таблице 6-3.  Бит
         DONE  должен всегда устанавливаться драйвером перед возвратом уп-
         равления к MS-DOS.
                                                         Таблица 6-3
                          Коды ошибок драйверов устройств
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Код          Ошибка                ¦Код        Ошибка
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДД
           0  Запись на устройство запрещена ¦ 8  Сектор не обнаружен
           1  Неопознанное устройство        ¦ 9  Нет бумаги в принтере
           2  Устройство не готово           ¦ A  Ошибка при записи
           3  Команда не опознана            ¦ B  Ошибка при чтении
           4  Неверно переданы данные        ¦ C  Общая ошибка
           5  Неверна длина заголовка запроса¦ D  Зарезервировано
           6  Ошибка при установке головки   ¦ E  Зарезервировано
           7  Неопознанный носитель данных   ¦ F  Недопустимая смена диска
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Примечание 1.  Все коды ошибок представлены в виде  шестнадцати-
                         ричных значений.
          Примечание 2.  Код ошибки 0FH поддерживается только в MS-DOS вер-
                         сии 3.0 и более поздних.

                                      - 6-29 -

                            Команды драйверов устройств

            Заголовок запроса,  как правило,  содержит не всю  информацию,
         которая требуется для большинства команд. Команд, которые не тре-
         буют дополнительной информации,  довольно  мало  --  это  команды
         INPUN/OUTPUT STATUS , FLUSH OUTPUT, OPEN/CLOSE DEVICE и REMOVABLE
         MEDIA.  Все остальные команды требуют гораздо больше  информации,
         чем  содержится в заголовке запроса.  Для каждой из этих команд к
         заголовку запроса  добавляется  дополнительная  информация.  Поле
         request.length  заголовка  запроса содержит при этом общий размер
         блока запроса (в байтах).
            Для облегчения  доступа  к  различным элементам блока запроса,
         опять-таки,  могут быть использованы структуры.  В листинге  6-10
         (листинге  драйвера  RDISK,  приведенного в конце главы) показано
         определение структур для тех команд,  которые обрабатываются этим
         драйвером.  Заметьте, что нам не нужно определять все поля в каж-
         дом блоке,  т.к. различные запросы часто используют похожие блоки
         запросов. Это обстоятельство довольно удобно, т.к. MASM не позво-
         ляет использовать одно и тоже имя более  одного  раза,  даже  для
         различных структур.

                                   Команда INIT
      ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
      є                                                                  є
      є  Команда INIT (0)                                                є
      є                                                                  є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                  є
      є  +00 : 23                   Длина         ¦ X ¦ Блок. драйверы   є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                  є
      є  +01 :                 Устройство         ЪДДД¬                  є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦ X ¦ Симв. драйверы   є
      є  +02 : 00                 Команда         АДДДЩ                  є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                                є
      є  +03 :                     Статус                                є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД          ЧТЕНИЕ      ЗАПИСЬ    є
      є                   Зарезервировано                                є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДД є
      є  +13 :       Количество устройств          ¦   ¦       ¦ X ¦     є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДД є
      є  +14 :                Адрес конца          ¦   ¦       ¦ X ¦     є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДД є
      є  +18 :          Команда/Адрес BPB          ¦ X ¦       ¦ X ¦     є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДД є
      є  +22 :           Номер устройства          ¦ X ¦       ¦   ¦     є
      є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДД є
      є                                                                  є
      є  Адрес таблицы BPB возвращается только блоковыми драйверами.     є
      є  Номер устройства поддерживается начиная с DOS 3.10.             є
      є                                                                  є
      ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

            Команда INIT  (инициализация) всегда является самой первой вы-
         зываемой командой и обрабатывается на этапе  установки  драйвера.

                                      - 6-30 -
         MS-DOS  выдает  эту команду для каждого драйвера только один раз.
         На  команду  INIT  возложена  ответственность  за  информирование
         MS-DOS об особых характеристиках драйвера и за выполнение необхо-
         димых действий по инициализации драйвера.  Последние  зависят  от
         типа устройства,  управляемого драйвером.  Возвращаемые драйвером
         характеристики также зависят от типа драйвера.
            Все драйверы  должны возвращать адрес последнего байта памяти,
         занимаемой драйвером и количество устройств,  управляемых драйве-
         ром.  Драйверы  символьных  устройств могут поддерживать не более
         одного устройства. Блоковые драйверы могут поддерживать несколько
         устройств (например, если в одном устройстве содержится несколько
         дисководов).  Кроме того,  драйвер может вернуть ноль в  качестве
         параметра  количества  поддерживаемых устройств,  для прекращения
         процесса инициализации.  Это может потребоваться,  к примеру, при
         обнаружении отсутствия устройства.  В такой ситуации драйвер дол-
         жен также установить адрес последнего используемого  байта равным
         CS:0 (текущий кодовый сегмент,  нулевое смещение) для того, чтобы
         MS-DOS могла использовать всю занимаемую драйвером память. В нор-
         мальной  ситуации адрес завершения представляет собой адрес (сег-
         мент и смещение) первого свободного после драйвера  байта памяти.
         MS-DOS  продолжает  загрузку  системы начиная со следующего после
         адреса завершения параграфа памяти (или начиная с адреса заверше-
         ния, если он приходится на границу параграфа).
            Третий параметр, определяемый командой INIT - это адрес табли-
         цы BPB. Этот указатель, возвращаемый MS-DOS командой INIT, предс-
         тавляет собой адрес таблицы,  которая сама представляет собой со-
         вокупность  указателей  на  блоки  параметров  BIOS.  Таблица BPB
         содержит по одному указателю на каждое устройство, поддерживаемое
         драйвером. Блок параметров BIOS (или, короче, BPB) это структура,
         которая определяет формат блокового устройства (см. рис.6-6). Так
         как  этот параметр имеет смысл только для блоковых устройств,  он
         не возвращается символьными  драйверами.  Однако  поле  указателя
         таблицы BPB в блоке запроса несет еще одну полезную нагрузку, ко-
         торая может быть использована обоими типами драйверов -  это поле
         содержит адрес командной строки драйвера. У нас еще будет возмож-
         ность подробнее обсудить назначение этого поля.
               СМЕЩЕНИЕ                СОДЕРЖАНИЕ                  РАЗМЕР
                (hex)
                        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД·
                  +0    ¦        Размер сектора в байтах        є   Слово
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +2    ¦    Количество секторов в кластере     є   Байт
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +3    ¦ Количество зарезервированных секторов є   Слово
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +5    ¦         Количество таблиц FAT         є   Байт
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +6    ¦    Количество элементов директория    є   Слово
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +8    ¦    Количество логических секторов     є   Слово
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +A    ¦          Описатель  носителя          є   Байт
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  +B    ¦    Количество секторов в одной FAT    є   Слово
                        ФНННННННННННННННННННННННННННННННННННННННј
                        Рисунок 6-6. Блок параметров BIOS

                                      - 6-31 -
            Последний параметр, уникальный для команды INIT, это номер ус-
         тройства.  Этот параметр,  который поддерживается только в MS-DOS
         версии 3.10 и более поздних,  используется для задания начального
         номера устройства.  К примеру, если драйвер должен управлять дис-
         ками C:  и D:,  содержимое этого поля будет равно 2 и  количество
         устройств будет равным двум. Если драйвер должен управлять только
         дисководом A:,  то номер устройства будет равен 0,  а  количество
         устройств 1. Эта возможность очень важна, так как она позволяет в
         конце концов заменять стандартные блоковые драйверы  на драйверы,
         устанавливаемые пользователем.
            Команда INIT является уникальной, так как из всех команд драй-
         вера,  она выполняется в среде, близкой к той, в которой выполня-
         ются  обычные программы.  В отличие от остальных команд,  команда
         INIT может использовать функции MS-DOS с 01H по 0CH и  30H.  Ука-
         занные функции позволяют драйверу выдать идентифицирующее сообще-
         ние во время установки и, если нужно, отобразить состояние конфи-
         гурации  драйвера.  Функция 30H ("Получить версию DOS") позволяет
         драйверу настроиться на определенную версию MS-DOS, что дает воз-
         можность разработчику писать драйверы, работающие с любой версией
         операционной системы.
            Другое сходство команды INIT  с  обычными  программами  MS-DOS
         заключается  в  том,  что  INIT  может прочитать командную строку
         драйвера и использовать ее для  конфигурации  драйвера.  Как  уже
         указывалось,  команда  DEVICE  в файле CONFIG.SYS имеет следующий
         формат :

                DEVICE=[d:][path]filename[.ext][ parameters]

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


                                      - 6-32 -
                                Команда MEDIA CHECK
                               ~~~~~~~~~~~~~~~~~~~~~
       ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
       є                                                                  є
       є Команда MEDIA CHECK (1)                                          є
       є                                                                  є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                   є
       є +00 : 19                   Длина         ¦ X ¦ Блок. драйверы    є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                   є
       є +01 : номер #         Устройство         ЪДДД¬                   є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦   ¦ Симв. драйверы    є
       є +02 : 01                 Команда         АДДДЩ                   є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                                 є
       є +03 :                     Статус                                 є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД          ЧТЕНИЕ      ЗАПИСЬ     є
       є                  Зарезервировано                                 є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДДД є
       є +13 :        Описатель  носителя          ¦ X ¦       ¦   ¦      є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДД є
       є +14 :         Состояние носителя          ¦   ¦       ¦ X ¦      є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДД є
       є +15 :           Адрес имени тома          ¦   ¦       ¦ X ¦      є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДДД є
       є                                                                  є
       є Состояние носителя : (-1) - носитель заменен, 0 - носитель неоп- є
       є ределен, 1 - носитель не изменялся.                              є
       є Имя тома возвращается только,если : (a) DOS версии не ниже 3.00, є
       є (b) установлен атрибут OCRM и (c) возвращаемый статус носителя   є
       є равен (-1).                                                      є
       є                                                                  є
       ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

            Команда MEDIA  CHECK  (Проверить  носитель) всегда выполняется
         блоковыми драйверами и никогда не используется для драйверов сим-
         вольных устройств. Эта команда используется MS-DOS для разрешения
         проблемы, которая может возникнуть при работе с устройствами, ис-
         пользующими сменный носитель  информации  :  носитель  (например,
         гибкий диск) может быть заменен.  При замене дискеты или ее экви-
         валента формат новой дискеты может отличаться от  предыдущего,  а
         уж содержимое новой дискеты несомненно будет другим.
            При замене дискеты MS-DOS должна настроиться на новую структу-
         ру диска : размер сектора, количество секторов и т.п. MS-DOS хра-
         нит  формат текущего диска в BPB и при смене носителя MS-DOS  по-
         требуется копия нового BPB.
            Даже если  дискета  заменена на имеющую тот же формат,  MS-DOS
         должна знать о том,  что замена диска произошла.  Каждый раз  при
         смене  носителя  директории и файлы новой дискеты наверняка будут
         отличаться от содержимого предыдущей дискеты, и MS-DOS должна бу-
         дет решать:  что делать с теми данными,  которые хранятся в буфе-
         рах, подготовленных для записи на предыдущий носитель.
            Для разрешения  всех  этих вопросов MS-DOS выдает драйверу ко-
         манду MEDIA CHECK,  спрашивая его о том, был ли заменен носитель.
         Драйвер должен вернуть на этот вопрос один из трех  ответов: "Да"
         (состояние  носителя  -1),  "Нет"  (состояние носителя 1) или "Не
         знаю" (состояние носителя 0).
            Важность этого вопроса  отражается  в  том  действии,  которое
         MS-DOS  предпринимает при получении ответа на него.  Если драйвер

                                      - 6-33 -
         отвечает "Нет,  носитель НЕ БЫЛ заменен", MS-DOS продолжает рабо-
         тать  так,  как и планировала, не проверяя, изменилось содержимое
         дискеты или нет.  Если драйвер отвечает "Да,  носитель БЫЛ  изме-
         нен", MS-DOS "выбрасывает" все хранящиеся  в буферах данные и за-
         прашивает у драйвера параметры  нового  носителя.  Наконец,  если
         драйвер отвечает что он сам не  знает  -  была  замена  или  нет,
         MS-DOS берет решение на себя. Если есть какие-либо данные, подго-
         товленные для записи на диск,  MS-DOS делает предположение о том,
         что это тот же самый диск.  В противном случае она делает предпо-
         ложение о том,  что произошла смена диска и  продолжает  работать
         так, как если бы драйвер вернул ответ "Носитель БЫЛ изменен".
            Для оказания помощи драйверу в решении вопроса о смене носите-
         ля MS-DOS передает драйверу текущий Media Descriptor  Byte  (байт
         описателя  носителя),  сокращенно MDB.  Этот байт входит в группу
         параметров,  называемую BPB (блок параметров BIOS), которая возв-
         ращается MS-DOS командами драйвера INIT и BUILD BPB. Каждому уни-
         кальному формату диска должен соответствовать свой описатель, хо-
         тя это и не всегда возможно (в разделе, описывающем команду BUILD
         BPB, этот вопрос обсуждается более подробно).
            Описатель носителя  хранится  в  первом  байте, находящемся на
         диске FAT (таблицы размещения файлов).  Кроме того,  младший байт
         значения  типа  диска  (см.  табл.11.5) представляет собой не что
         иное,  как MDB.  Подробнее о FAT и типах дисков Вы можете узнать,
         прочитав 11 главу.
            При решении вопроса о том,  была ли замена  носителя,  драйвер
         может использовать следующую логику :

           1. Если устройство не поддерживает возможность  смены  носителя
              (например,  если это жесткий диск или RAM-диск),  то драйвер
              должен ответить "Нет,  замены носителя не было". В противном
              случае переход к шагу 2.

           2. Фирма Microsoft утверждает, что на замену дискеты  требуется
              не менее двух секунд.  Принимая этот факт во внимание, драй-
              вер должен проверить системные часы и, если с момента преды-
              дущего  обращения к диску прошло менее двух секунд,  вернуть
              ответ "Нет,  замены носителя не было".  Конечно,  этот метод
              требует,чтобы драйвер всегда сохранял время обращения к дис-
              ку. Если прошло более двух секунд, то переход к шагу 3. Оче-
              видно,  что  если нет возможности считывать системное время,
              то данный шаг можно опустить.

           3. Иногда сам дисковод может иметь  возможность  информирования
              драйвера о происшедшей замене носителя.  Некоторые дисководы
              оборудованы электронной схемой, подающей сигнал, если дверца
              дисковода  открывалась с момента последнего обращения к дис-
              ку. Если драйвер обслуживает именно такой дисковод и послед-
              ний сообщает,  что дверца не открывалась,  то драйвер должен
              ответить "Нет,  замены носителя не было".  Если дверца  была
              открыта, то переход к шагу 4.
                 Бывают дисководы,  в которых двигатели включаются  только
              при  обращении  к дискете и выключаются,  выдержав некоторый
              временной интервал.  Если такой дисковод позволяет считывать
              состояние  двигателя и двигатель еще работает с момента пос-
              леднего обращения, то это позволяет сделать вывод о том, что
              дискета не заменялась и драйвер должен ответить "Нет, замены
              носителя не было".  Однако, встречаются дисководы, двигатели
              которых включаются в тот момент,  когда вставляется дискета,
              что может сделать результаты данной  проверки некорректными.

                                      - 6-34 -

           4. Драйвер должен прочесть с  диска описатель  носителя.  Если
              этот  MDB  отличается от переданного драйверу при вызове ко-
              манды MEDIA CHECK описателя,  то драйвер  должен  ответить
              "Да, была замена носителя". В противном случае переход к ша-
              гу 5.

           5. Драйвер должен прочесть с диска идентификатор тома. Если  он
              отличается  от  того,  который  хранится драйвером с момента
              последней команды BUILD BPB, то драйвер должен ответить "Да,
              была замена носителя". Иначе переход к шагу 6.

           6. Драйвер должен ответить "Не знаю, была ли замена носителя".

            Может случиться так, что невозможно реализовать некоторые эта-
         пы описанного алгоритма. Если по каким-либо причинам Вы не можете
         определить,  произошла ли замена дискеты, то лучшим ответом будет
         "Не знаю,  была ли замена носителя". Конкретный метод определения
         замены носителя будет зависеть как от особенностей дисковода, так
         и от квалификации программиста.
            Если драйвер работает с MS-DOS версии 3.0 или выше, то команда
         MEDIA CHECK может вернуть еще некоторую информацию. В том случае,
         когда  драйвер  поддерживает  команды  OPEN/CLOSE/REMOVABLE MEDIA
         (установлен бит 11 в слове атрибутов драйвера)  и  команда  MEDIA
         CHECK  собирается ответить "Да,  была замена носителя" (состояние
         носителя : -1), тогда драйвер обязан вернуть указатель на имя то-
         ма предыдущего диска (см.  главу 11 для получения сведений о фор-
         мате и расположении имени тома).  Если драйвер не знает имя  тома
         предыдущего диска (например, если обращение к команде MEDIA CHECK

                                 Команда BUILD BPB
       ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
       є Команда BUILD BIOS PARAMETER BLOCK (2)                           є
       є                                                                  є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                   є
       є +00 : 22                   Длина         ¦ X ¦ Блок. драйверы    є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                   є
       є +01 : номер #         Устройство         ЪДДД¬                   є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦   ¦ Симв. драйверы    є
       є +02 : 02                 Команда         АДДДЩ                   є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                                 є
       є +03 :                     Статус                                 є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД          ЧТЕНИЕ      ЗАПИСЬ     є
       є                  Зарезервировано                                 є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДДД є
       є +13 :        Описатель  носителя          ¦ X ¦       ¦   ¦      є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДД є
       є +14 :           Указатель на FAT          ¦ X ¦       ¦   ¦      є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДД є
       є +18 :           Указатель на BPB          ¦   ¦       ¦ X ¦      є
       є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДДД є
       є                                                                  є
       є Поле по смещению 14 от начала блока запроса содержит указатель нає
       є FAT для IBM-стандартных устройств (бит 13 в слове атрибутов равенє
       є нулю)  или указатель на "мусор" для NONIBM/NONFAT устройств  (битє
       є 13 слова атрибутов равен 1).                                     є
       ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

                                      - 6-35 -
         происходит впервые), то драйвер должен вернуть указатель на стро-
         ку  "NO NAME"  оканчивающуюся нулевым байтом (т.е.  "NO",  пробел,
         "NAME", четыре пробела, ноль).
            Команда BUILD  BPB (построить блок параметров BIOS) всегда вы-
         полняется блоковыми драйверами  и  никогда  не  используется  для
         драйверов  символьных  устройств.  Во всех случаях,  когда MS-DOS
         проинформирована или решила сама, что носитель заменен, она долж-
         на получить параметры нового носителя. Выдавая команду BUILD BPB,
         MS-DOS просит драйвер вернуть указатель на блок  параметров BIOS,
         содержащий  новые  значения  (содержимое  полей  BPB  показано на
         рис.6-6).
            Существует важное различие между адресом BPB, возвращаемым ко-
         мандой BUILD BPB и указателем таблицы BPB,  возвращаемым командой
         INIT.  В то время,  как команда BUILD BPB возвращает указатель на
         сам блок параметров BIOS,  команда INIT возвращает адрес  таблицы
         указателей на BPB. Хотя различие между указателем и указателем на
         указатели очевидно, оно может быть источником ошибок.
            Подобно команде MEDIA CHECK, команда BUILD BPB может иметь де-
         ло с идентификатором тома.  В MS-DOS версии 3.0 и выше  драйверы,
         поддерживающие возможность замены носителя и имеющие атрибут OCRM
         (бит 11 слова атрибутов равен 1),  должны считывать  и  сохранять
         имя тома. Это имя позже будет возвращаться последующими обращени-
         ями к команде MEDIA CHECK.
            Получение команды BUILD BPB может восприниматься драйвером как
         заявление системы о том,  что по ее мнению произошла замена носи-
         теля. Если драйвер поддерживает счетчик количества "открываний" и
         "закрываний",  выполненных для устройства командами OPEN DEVICE и
         CLOSE DEVICE, то пришла пора обнулить его.

                          Получение блока параметров BIOS

             Не рассматривая механизма возврата BPB,  мы должны решить за-
         дачу  определения содержимого блока параметров BIOS.  Описываемые
         методы применимы не только к команде BUILD BPB,  но и  к  команде
         INIT.  В  простейшем случае драйвера устройства,  поддерживающего
         только один тип носителя (например драйвер RAM-диска), содержимое
         BPB может быть закодировано в теле самого драйвера.  К несчастью,
         при работе с реальными дисками, включая жесткие диски, не все так
         просто и драйвер обязан определять содержимое BPB.
            Как правило, BPB является частью блока начальной загрузки, как
         показано  на рис.6-7. В этом случае драйвер должен найти и прочи-
         тать этот блок,  выбрать оттуда блок параметров BIOS и возвратить
         адрес последнего. Практически во всех случаях блок начальной заг-
         рузки располагается в самом первом логическом секторе диска (т.е.
         сектора,  имеющего  номер  0).  Преобразование номера логического
         сектора в координаты физического сектора зависит от характеристик
         устройства и должно быть описано в документации по этому устройс-
         тву. Драйвер должен проверить структуру этого сектора, чтобы убе-
         диться, что он действительно содержит блок начальной загрузки.
            Если первый  логический  сектор  не содержит корректного блока
         начальной  загрузки,  например, как в дисках, отформатированных в
         MS-DOS версий до 2.0,то драйвер должен считать первый сектор таб-
         лицы размещения файлов (FAT).  К счастью,  MS-DOS версий  до  2.0
         поддерживали только  несколько форматов, каждый из которых опреде-
         лялся в первом секторе  FAT второго логического сектора диска. Са-
         мый первый байт первого  сектора  FAT  содержит байт описателя но-

                                      - 6-36 -
                  СМЕЩЕНИЕ              СОДЕРЖАНИЕ                РАЗМЕР
                   (hex)
                        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД·
                    +00 ¦  Команда перехода на код загрузчика   є 3 байта
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                    +03 ¦       Имя и версия изготовителя       є 8 байт
                  ЦД    ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +0B ¦        Размер сектора в байтах        є Слово
                  є     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +0D ¦    Количество секторов в кластере     є Байт
                  є     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +0E ¦ Количество зарезервированных секторов є Слово
         БЛОК     є     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +10 ¦         Количество таблиц FAT         є Байт
      ПАРАМЕТРОВ Д¶     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +11 ¦    Количество элементов директория    є Слово
         BIOS     є     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +13 ¦    Количество логических секторов     є Слово
                  є     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +15 ¦          Описатель  носителя          є Байт
                  є     ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                  є +16 ¦    Количество секторов в одной FAT    є Слово
                  УД    ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                    +18 ¦    Количество секторов на дорожке     є Слово
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                    +1A ¦   Количество головок чтения/записи    є Слово
                        ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¶
                    +1C ¦      Количество скрытых секторов      є Слово
                        ФНННННННННННННННННННННННННННННННННННННННј

          Рисунок 6-7. Содержимое первых 30 байт блока начальной загрузки
          сителя, который можно использовать для определения соответству-
          ющего содержимого BPB,  возвращаемого к MS-DOS.  Версии  MS-DOS
          до 2.0 используют описатели 0FEH и 0FFH. В главе 11 представлен
          список различных значений типов дисков, из которых берется MDB.
            Выполняя этот процесс,  Вам следует помнить, что просто чтение
         диска не может гарантировать правильных  результатов.  Если  уст-
         ройство и  драйвер  поддерживают  несколько форматов (например, с
         различными размерами сектора),  то драйверу  может  потребоваться
         несколько попыток чтения с разными форматами для того,  чтобы об-
         наружить корректный формат. После того, как сформирован BPB и оп-
         ределен  формат данного диска драйвер,  поддерживающий устройство
         со сменным носителем  (имеющий  атрибут  OCRM),  обязан  получить
         идентификатор  тома данного диска. Найти его можно, обратившись к
         корневому директорию, как описано в главе 11.

            Вкратце, последовательность обработки команды BUILD BPB следу-
          ющая :

           1. Драйвер должен прочитать блок начальной загрузки (обычно на-
              ходящийся в первом логическом секторе диска - сектор  #0)  и
              проверить его на наличие блока параметров BIOS. Если BPB об-
              наружен, то переход к шагу 3, иначе переход к шагу 2.

           2. Драйвер должен прочитать первый сектор FAT для  того,  чтобы
              получить  байт описателя носителя.  Полагаясь на этот MDB,

                                      - 6-37 -
              драйвер должен сконструировать соответствующий BPB (см. гла-
              ву 11 о соответствии между MDB и BPB).

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

            Для выполнения  этого  алгоритма,  драйвер должен иметь буфера
         для хранения копии BPB и имени тома, а также буфер, предназначен-
         ный для считывания туда сектора с диска.
            Мы опустили  из  рассмотрения  параметры,  которые  передаются
         драйверу при обращении к нему с командой BUILD  BPB.  Игнорируйте
         их. Один из этих параметров - это описанный ранее описатель носи-
         теля,  который в данной ситуации не имеет никакого значения,  так
         как  данная команда возвращает MS-DOS новое его значение.  Второй
         параметр - это адрес буфера,  который либо не содержит ничего су-
         щественного (если бит 13, NONIBM атрибут, равен 1), либо содержит
         копию первого сектора FAT (если бит 13 сброшен). В последнем слу-
         чае,  т.е.  если там содержится FAT, этот буфер никоим образом не
         должен быть модифицирован,  а так как драйвер обязан  иметь  свой
         буфер,  куда будет считываться блок начальной загрузки, то на бу-
         фер,  передаваемый при вызове команды BUILD BPB можно не обращать
         внимания.
            Напоследок представляется важным отметить,  что в  отличие  от
         BPB  описатель  носителя не обеспечивает однозначного определения
         формата диска.  Однако,  MS-DOS версии 3.0 и выше не будут обнов-
         лять свои внутренние структуры, ассоциированные с данным дисково-
         дом,  до тех пор, пока байт описателя носителя не станет отличным
         от предыдущего MDB.  Даже несмотря на то, что MS-DOS версии 3.0 и
         выше не обращают внимание на действительное значение MDB, драйвер
         должен вернуть новый MDB при смене формата дискеты.
            Команды INPUT, OUTPUT и  OUTPUT & VERIFY (команды 4,  8 и 9  -
         "Ввод", "Вывод"  и  "Вывод  с проверкой",  соответственно) всегда
         требуются для всех драйверов.  При помощи этих команд выполняется
         передача данных между MS-DOS и устройством.
            Команды IOCTL INPUT и IOCTL OUTPUT (коды 3 и 12 - "Ввод команд
         управления" и "Вывод команд управления", соответственно) являются
         дополнительными,  требующимися только при установленном IOCTL ат-
         рибуте (бит 14 слова атрибутов драйвера). Эти команды применяются
         как с блоковыми так и с символьными драйверами и обеспечивают пе-
         редачу данных между MS-DOS и драйвером.
            Команда OUTPUT UNTIL BUSY (код 16 - "Вывод  пока  не  занято")
         является необязательной командой и используется исключительно для
         символьных драйверов,  имеющих атрибут NONIBM/OTB (бит  13).  Эта
         команда обеспечивает передачу данных от MS-DOS к устройству.  За-
         метьте также,  что эта команда не документирована в IBM Technical
         Reference Manual для PC-DOS версии 3.30.
            Команды OUTPUT  и  OUTPUT & VERIFY устанавливаются комбинацией
         пятого бита IOCTL (бит 5 - подготовленный/неподготовленный режим)
         и  опцией VERIFY.  Если установлен режим проверки,  то весь вывод
         данных обеспечивается командой OUTPUT & VERIFY. Если режим провер-
         ки не установлен, то используется обычная команда OUTPUT. Перевод
         драйвера в неподготовленный режим (при установке  IOCTL  бита  5)
         позволяет осуществлять многобайтные передачи.
            Существует комбинация режимов,  которая должна была бы вызвать
         использование команды OUTPUT UNTIL BUSY, но не делает этого. Эта
         комбинация включает режим без проверки (так что команда OUTPUT &

                                      - 6-38 -
                              Команды INPUT и OUTPUT
                             ~~~~~~~~~~~~~~~~~~~~~~~~
      ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
      є                                                                  є
      є Команды INPUT и OUTPUT (3,4,8,9,12,16)                           є
      є                                                                  є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                   є
      є +00 : 22                   Длина         ¦ X ¦ Блок. драйверы    є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                   є
      є +01 : номер #         Устройство         ЪДДД¬                   є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦ X ¦ Симв. драйверы    є
      є +02 : команда            Команда         АДДДЩ                   є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                                 є
      є +03 :                     Статус     ЪДДДДДДДДДДДДДДДДДДДДДДДДДД¬є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД     ¦  Команды :               ¦є
      є                  Зарезервировано     ¦                          ¦є
      є                                      ¦  03 : IOCTL INPUT        ¦є
      є                                      ¦  04 : INPUT              ¦є
      є                                      ¦  08 : OUTPUT             ¦є
      є                                      ¦  09 : OUTPUT & VERIFY    ¦є
      є                                      ¦  12 : IOCTL OUTPUT       ¦є
      є                                      ¦  16 : OUTPUT UNTIL BUSY  ¦є
      є                                      АДДДДДДДДДДДДДДДДДДДДДДДДДДЩє
      є                                                                  є
      є                                           ЧТЕНИЕ      ЗАПИСЬ     є
      є                                                                  є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДДДДє
      є +13 :        Описатель  носителя          ¦ X ¦       ¦   ¦      є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДДє
      є +14 :               Адрес буфера          ¦ X ¦       ¦   ¦      є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДДє
      є +18 : Количество байтов/секторов          ¦ X ¦       ¦ X ¦      є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДДє
      є +20 :           Начальный сектор          ¦ X ¦       ¦   ¦      є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДДє
      є +22 :      Указатель на имя тома          ¦   ¦       ¦ X ¦      є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДДДДє
      є                                                                  є
      є Поле  по смещению  18 от начала  блока запроса содержит на  входеє
      є требуемое количество байтов/секторов.  Драйвер должен поместить вє
      є это поле фактическое количество переданных секторов или байтов.  є
      є Указатель на имя тома возвращается только в MS-DOS версии  3.00 иє
      є выше  при условии,  что возвращается ошибка  0FH -- "Недопустимаяє
      є замена диска".                                                   є
      є                                                                  є
      ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

         VERIFY не будет использоваться), неподготовленный режим (позволя-
         ющий многобайтные передачи) и устройство,  поддерживающее команду
         OUTPUT UNTIL BUSY.  Однако, при тестировании этого режима обнару-
         жилось,  что команда OUTPUT UNTIL BUSY не выдается никогда, чем и
         объясняется,  наверное,  почему  IBM опустила эту команду в своей
         документации.
            Все эти команды имеют общую структуру блока запроса,  но отли-
         чаются типом запрашиваемой операции ввода/вывода и типом драйвера
         устройства. Ниже перечислены основные параметры, используемые при
         вызове команд ввода/вывода :

                                      - 6-39 -

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

                КОМАНДЫ                        ИСТОЧНИК      ПОЛУЧАТЕЛЬ

                INPUT                          Устройство    Буфер
                OUTPUT                         Буфер         Устройство
                OUTPUT VERIFY                  Буфер         Устройство
                OUTPUT UNTIL BUSY              Буфер         Устройство
                IOCTL INPUT                    Драйвер       Буфер
                IOCTL OUTPUT                   Буфер         Драйвер

            * Адрес источника или получателя со стороны MS-DOS представля-
              ет собой адрес буфера,  который либо содержит данные для вы-
              вода (команды OUTPUT),  либо будет заполнен данными (команды
              INPUT).

            * "Количество байт/секторов" определяет сколько байт (для сим-
              вольных устройств и в командах IOCTL) или  секторов (команды
              INPUT, OUTPUT и OUTPUT&VERIFY) будет (или было) передано.

            * Только  для  блоковых драйверов задаются параметры "Устройс-
              тво" и "Начальный сектор", уточняющие местонахождение источ-
              ника (для INPUT) или получателя (для OUTPUT).

            * Параметр "Байт описателя носителя" (только для блоковых ус-
              тройств) может быть использован для определения формата дис-
              ка или того факта, что носитель был заменен.
            Как только драйвер определил источника и получателя, он выпол-
         няет  передачу  данных.  После выполнения передачи драйвер должен
         вернуть фактическое количество переданных  байтов  или  секторов.
         Даже если возникла ошибка и установлен индикатор ошибки в возвра-
         щаемом слове состояния, MS-DOS считает, что возвращаемое значение
         параметра  "Количество байтов/секторов" корректно.  Если драйверу
         не удалось обновить этот параметр, то возвращаемое значение будет
         таким  же  как  и  переданное драйверу на входе.  Вы должны также
         знать,  что даже если передача прошла успешно,  счетчик  все-таки
         может  иметь неверное значение.  Это происходит при возникновении
         т.н. "перекрытия".
            Перекрытие (для  блоковых  драйверов)  возникает в том случае,
         если передается большее количество байт, чем то которое можно ад-
         ресовать с помощью сегментного адреса буфера.  Приведем следующий
         пример. Пусть драйвер пересылает 64 сектора по 512 байт каждый из
         буфера начиная со смещения 8000H. Таким образом, общее количество
         байт, которые требуется передать, составит 32768 (8000H). Так как
         начальное  смещение в буфере равно 8002H,  то смещение последнего
         байта будет иметь заведомо некорректное значение 10002H. В подоб-
         ных случаях, т.е. при возникновении перекрытий, драйвер не должен
         пытаться передавать недостижимую порцию данных.
            Каждая команда  имеет свои особенные требования для выполнения
         передачи и отличаются возвращаемым значением статуса  и счетчика.
         Эти требования описаны ниже для каждой из команд.

                                      - 6-40 -

         КОМАНДЫ CTL INPUT и CTL OUTPUT (3 и 12).
            Это простейшие команды,  обычно требуемые только для  передачи
         данных самому  драйверу  (не  устройству) или получения данных от
         него.  Для MS-DOS эти данные не имеют никакого значения, и, более
         того,  могут  быть проигнорированы самим драйвером,  если ему так
         захочется.  Ответственность за обработку переданных таким образом
         данных лежит только на драйвере и прикладной программе.  Как пра-
         вило,  используются для изменения режимов работы  драйвера  и/или
         устройства,  хотя возможны и другие варианты. Важно только, чтобы
         драйвер не забывал правильно устанавливать  возвращаемое значение
         счетчика переданных байтов.

         КОМАНДЫ INPUT OUTPUT (4 и 8).
         Для большинства  символьных  драйверов  логика  обработки  команд
         INPUT и OUTPUT весьма незамысловата.  Если передача прошла успеш-
         но,  то устанавливается бит DONE в  слове  состояния  драйвера  и
         драйвер возвращает управление. Если возникла какая-либо проблема,
         то в  слово  состояния  записывается  код  соответствующей ошибки
         (см.табл.6-3), устанавливается счетчик и возвращается управление.

            Если символьное  устройство  не имеет готовых данных на момент
         выдачи команды INPUT,  драйвер может либо подождать  или  вернуть
         ошибку "Устройство не готово". При выводе данных, если устройство
         не может их принять,  драйвер также может вернуть эту ошибку. Од-
         нако ошибка "Устройство не готово" обычно используется для указа-
         ния того, что устройство выключено или по каким-либо причинам не-
         доступно.  Использование  этой  ошибки  всего  лишь для индикации
         неготовности данных является не слишком хорошим решением, так как
         получив ошибку "Устройство не готово",  MS-DOS запросто может вы-
         дать оператору запрос на вмешательство.
            Логика  работы  блоковых  драйверов  при  выполнении  операций
         ввода/вывода более сложная. Как правило, драйвер должен выполнять
         преобразование номера начального сектора в координаты физического
         сектора,  обычно состоящие из номера цилиндра  (дорожки),  номера
         головки  и номера физического сектора на дорожке.  Возможно,  что
         драйверу придется выполнить операцию перевода головки  чтения/за-
         писи  на соответствующую дорожку перед началом передачи и,  может
         быть, в процессе самой передачи секторов. Более подробно устройс-
         тво диска описано в главе 11.
            Более того, устройства, подобные дисководам, являются источни-
         ком множества ошибок (см.табл.6-3), таких как "Запись на устройс-
         тво запрещена",  "Неверно переданы данные (ошибка CRC)",  "Ошибка
         при установке головки", "Ошибка при чтении", "Ошибка при записи",
         и даже такая звучная как "Общая ошибка". Обычно при возникновении
         ошибки драйвер фиксирует код ошибки в слове состояния, устанавли-
         вает счетчик успешно переданных секторов и возвращает управление.
         Однако одна ошибка требует дальнейшего анализа и обработки  - это
         "Недопустимая смена диска".
            Ошибка "Недопустимая смена диска" воспринимается MS-DOS версии
         3.0 и выше и только в том случае, когда MS-DOS знает что она име-
         ет дело с устройством, поддерживающем замену носителя (установлен
         атрибут OCRM в слове атрибутов драйвера).  Отличие этой ошибки от
         остальных заключается в том,  что если драйвер информирует MS-DOS
         о недопустимой замене носителя,  MS-DOS  должна  знать  с  каким,
         собственно,  диском намеревался работать драйвер.  Эта информация
         определяется именем тома ожидаемого диска,  указатель на  которое

                                      - 6-41 -
         должен вернуть драйвер.  Как и в команде MEDIA CHECK,  если соот-
         ветствующее имя драйверу не известно,  он  должен  вернуть  адрес
         строки "NO NAME".
            Как узнает драйвер о недопустимой замене носителя?  Если драй-
         вер ведет счетчик количества открытий и закрытий,  выполненных на
         устройстве (командами 13 и 14) и  диск  заменяется  пользователем
         (что  определяется другим форматом,  другим описателем носителя и
         т.п.) при количестве открытий превышающем количество закрытий, то
         делается  вывод  о  том,  что замена носителя была недопустимой и
         формируется ошибка "Недопустимая смена диска".

         КОМАНДА OUTPUT & VERIFY (9).
         Команда OUTPUT & VERIFY(вывод с проверкой) применяется только для
         тех устройств,  в которых возможно считывание данных после их за-
         писи  на устройство для того,  чтобы убедиться в корректности вы-
         полненной операции.  Для таких устройств  (например  для  дисков)
         драйвер  должен  выводить данные (также как и по команде OUTPUT),
         считывать их обратно (так же как по INPUT) и  сравнить  считанные
         данные с теми,  которые были записаны. Если обнаружена ошибка, то
         драйверу следует не пытаться повторить  неудавшуюся  операцию,  а
         сообщить  об  этой  ситуации  MS-DOS,  вернув соответствующий код
         ошибки (см.табл.6-3) и количество успешно переданных  байтов/сек-
         торов.

            Как и   в   случае   команды    BUILD BPB,   обработка команды
         OUTPUT & VERIFY требует наличия у драйвера внутреннего буфера для
         считывания проверяемых данных.  Если устройство не позволяет счи-
         тывать данные обратно,  то данная команда  должна  обрабатываться
         также как и команда OUTPUT (команда 8).

         КОМАНДА OUTPUT UNTIL BUSY (16).
            Команда OUTPUT UNTIL BUSY представляет еще одну  разновидность
         команды OUTPUT.  Эта команда,  которая используется только с сим-
         вольными устройствами, драйверы которых имеют атрибут OTB (бит 13
         слова атрибутов),  позволяет программам передавать большие порции
         данных устройствам,  которые имеют внутренние буфера  (таких  как
         принтеры).  Драйвер такого устройства должен посылать данные либо
         до тех пор пока они не кончатся, либо пока устройство в состоянии
         их принять. Очень важно, чтобы такой драйвер корректно устанавли-
         вал счетчик переданных байтов, так чтобы MS-DOS знала какое коли-
         чество данных уже передано.  Обратите внимание,  что для этой ко-
         манды не  является  ошибочной  ситуация,  когда  передано  меньше
         данных, чем было запрошено.
            Команда NONDESTRUCTIVE INPUT WITHOUT WAIT (неразрушающее  счи-
         тывание  без  ожидания) требуется только для драйверов символьных
         устройств и не используется для блоковых драйверов.  Хотя эта ко-
         манда похожа на обычную символьную команду INPUT, она все же име-
         ет несколько заметных отличий :
            * Отсутствует буфер данных  и  счетчик  количества  переданных
              данных.  При  вызове  этой команды требуемое количество байт
              всегда равно 1 и если устройство  готово  предоставить  байт
              данных,  он  возвращается  в  поле  "Считанный из устройства
              байт" блока запроса.

            * Нет ожидания. Если устройство не готово предоставить очеред-

                                      - 6-42 -
                     Команда NONDESTRUCTIVE INPUT WITHOUT WAIT
                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      ЙННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
      є                                                                 є
      є Команда NONDESTRUCTIVE INPUT WITHOUT WAIT (5)                   є
      є                                                                 є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                  є
      є +00 : 14                   Длина         ¦   ¦ Блок. драйверы   є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                  є
      є +01 :                 Устройство         ЪДДД¬                  є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦ X ¦ Симв. драйверы   є
      є +02 : 05                 Команда         АДДДЩ                  є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                                є
      є +03 :                     Статус                                є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД          ЧТЕНИЕ      ЗАПИСЬ    є
      є                  Зарезервировано                                є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДДДє
      є +13 :  Считанный из устр-ва байт          ¦   ¦       ¦ X ¦     є
      є ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДДДє
      є                                                                 є
      ИНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј
              ной символ, то драйвер должен установить бит BUSY в дополне-
              ние к биту DONE в слове состояния и  незамедлительно вернуть
              управление.

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

            Эта команда  предназначена  для  того,  чтобы  MS-DOS   могла,
         во-первых, определить наличие данных, не используя команду INPUT,
         которая может привести к длительному ожиданию данных,  и, во-вто-
                        Команды STATUS и FLUSH INPUT/OUTPUT
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
         єКоманды STATUS и FLUSH INPUT/OUTPUT (6,7,10,11)               є
         є                                                              є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                є
         є+00 : 13                   Длина         ¦   ¦ Блок. драйверы є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                є
         є+01 : номер #         Устройство         ЪДДД¬                є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦ X ¦ Симв. драйверы є
         є+02 : команда            Команда         АДДДЩ                є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                              є
         є+03 :                     Статус       ЪДДДДДДДДДДДДДДДДДДДД¬ є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       ¦ КОМАНДЫ :          ¦ є
         є                 Зарезервировано       ¦                    ¦ є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       ¦  6:  INPUT STATUS  ¦ є
         є                                       ¦  7:  INPUT FLUSH   ¦ є
         є                                       ¦ 10:  OUTPUT STATUS ¦ є
         є                                       ¦ 11:  OUTPUT FLUSH  ¦ є
         є                                       АДДДДДДДДДДДДДДДДДДДДЩ є
         ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

                                      - 6-43 -
         рых,  анализировать следующий символ в буфере без изъятия его от-
         туда.
            Команды I/O STATUS и I/O FLUSH (команды 6,10 и 7,11, соответс-
         твенно) требуются только для символьных устройств и не  использу-
         ются блоковыми.
            Команды INPUT  STATUS и INPUT FLUSH имеют смысл только для тех
         драйверов символьных устройств,  которые поддерживают управляемые
         прерываниями очереди вводимых данных,  хотя эти команды могут вы-
         зываться для любого символьного драйвера.Команда INPUT STATUS ис-
         пользуется для индикации состояния очереди следующим образом:

            * Если очередь есть,  но она пуста,  то драйвер должен устано-
              вить  биты  DONE и BUSY в слове состояния и вернуть управле-
              ние.

            * Если в очереди есть символы,  доступные для чтения, то драй-
              вер должен установить бит DONE,  сбросить бит BUSY и вернуть
              управление.

            * Если очередь не поддерживается, то драйвер должен установить
              бит DONE,  сбросить бит BUSY в слове состояния и вернуть уп-
              равление. Это выглядит странным -  уведомлять MS-DOS о нали-
              чии символа, когда даже очереди нет.Объяснение заключается в
              том, что после такого ответа MS-DOS выдаст команду INPUT для
              считывания символа.  Если же этого не сделать, то MS-DOS бу-
              дет продолжать опрашивать  статус  ввода бесконечно, так как
              из-за отсутствия очереди статус всегда будет одним и тем же.

            Команда INPUT FLUSH  применяется для уведомления драйвера о не-
         обходимости удаления всех находящихся в данный момент  во входной
         очереди  символов.  После  очистки очереди (если таковая имеется)
         драйвер должен установить бит DONE и вернуть управление.  При об-
         работке этой команды не должно возникать никаких ошибок, по край-
         ней мере MS-DOS предполагает,  что эта команда всегда завершается
         успешно.
            Команда OUTPUT STATUS используется для проверки состояния  вы-
         ходной  очереди или устройства.  Если драйвер не поддерживает вы-
         ходную очередь, то следует, по возможности, вернуть состояние са-
         мого  устройства.  Состояние  определяется  битом BUSY ("занято")
         слова состояния (состояние "занято" означает что вывод задержива-
         ется).  Установив состояние, драйвер должен установить бит DONE и
         вернуть управление.
            Команда OUTPUT FLUSH предназначена для указания драйверу необ-
         ходимости удалить все находящиеся в выходной очереди символы (ес-
         ли очередь поддерживается) и, если это возможно, немедленно прек-
         ратить любые операции  вывода.  После  выполнения  этих  действий
         драйвер  должен  установить бит DONE в слове состояния драйвера и
         вернуть управление MS-DOS.
            Команды DEVICE OPEN и DEVICE CLOSE (команды 13 и  14) являются
         необязательными командами, поддерживаемые MS-DOS версий 3.0 и вы-
         ше, и используются только если драйвер имеет атрибут OCRM (бит 11
         слова  атрибутов драйвера равен 1).  Однако Microsoft рекомендует
         использовать эти команды во всех новых создаваемых драйверах.

                                      - 6-44 -
                    Команды DEVICE OPEN/CLOSE и REMOVABLE MEDIA
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
         є                                                              є
         єКоманды DEVICE OPEN/CLOSE и REMOVABLE MEDIA (13,14,15)        є
         є                                                              є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                є
         є+00 : 13                   Длина         ¦ X ¦ Блок. драйверы є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                є
         є+01 : номер #         Устройство         ЪДДД¬                є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦   ¦ Симв. драйверы є
         є+02 : команда            Команда         АДДДЩ                є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                              є
         є+03 :                     Статус       ЪДДДДДДДДДДДДДДДДДДДД¬ є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       ¦ КОМАНДЫ :          ¦ є
         є                 Зарезервировано       ¦                    ¦ є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       ¦ 13 : DEVICE OPEN   ¦ є
         є                                       ¦ 14 : DEVICE CLOSE  ¦ є
         є                                       ¦ 15 : REMOVABLE     ¦ є
         є                                       ¦      MEDIA CHECK   ¦ є
         є                                       АДДДДДДДДДДДДДДДДДДДДЩ є
         ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј


            Команда REMOVABLE  MEDIA  (15) является дополнительной командой
         для блоковых драйверов в MS-DOS 3.0 и выше и  используется  только
         при наличии у драйвера атрибута OCRM (бит 11 слова атрибутов). Эта
         команда также рекомендуется к использованию  фирмой  Microsoft  во
         всех новых драйверах.

            Условия, при   которых   происходит   обращение   к   командам
         DEVICEOPEN и DEVICECLOSE,  описаны выше в подразделе "Слово атри-
         бутов", подзаголовок "БИТ 11 : OCRM".
            Для блоковых устройств со сменным носителем информации эти ко-
         манды  могут  использоваться для отслеживания количества открытых
         на устройстве файлов, позволяя, таким образом, обнаруживать ситу-
         ацию  недопустимой  замены носителя (которая возникает при замене
         диска, на котором еще имеются открытые файлы).
            Для символьных устройств эти команды могут  использоваться для
         предотвращения одновременного доступа различных программ к одному
         устройству (такому как принтер) или для  обеспечения возможностей
         перед и после обработки устройства (например, операций загрузки и
         сброса принтера).
            Команда REMOVABLEMEDIA  может  быть выдана прикладной програм-
         мой,  используя подфункцию "Проверка заменяемости носителя" функ-
         ции IOCTL (подфункция 08H функции 44H).  При вызове  этой функции
         прикладная программа должна задать номер интересующего ее  диско-
         вода.  Получив команду REMOVABLEMEDIA,  драйвер должен определить
         имеет ли упомянутое устройство возможность замены носителя и вер-
         нуть статус битом BUSY слова состояния драйвера.  Если устройство
         не поддерживает смены носителя, то драйвер должен установить  бит
         BUSY, в противном случае сбросить его.
            Команда GENERIC IOCTL (19) является  дополнительной  командой,
         поддерживаемой  MS-DOS начиная с версии 3.20.  Использование этой
         команды разрешается установленным в 1 атрибутом  GIOCTL  (бит  6)
         слова состояния.

                                      - 6-45 -
                               Команда GENERIC IOCTL
                              ~~~~~~~~~~~~~~~~~~~~~~~
         ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
         є                                                              є
         є  Команда GENERIC IOCTL (19)                                  є
         є                                                              є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД  ЪДДД¬                     є
         є  +00 : 23                   Длина  ¦ X ¦ Блок. драйверы      є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД  АДДДЩ                     є
         є  +01 : номер #         Устройство  ЪДДД¬                     є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД  ¦   ¦ Симв. драйверы      є
         є  +02 : 19                 Команда  АДДДЩ                     є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                            є
         є  +03 :                     Статус                            є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД   ЧТЕНИЕ      ЗАПИСЬ       є
         є                   Зарезервировано                            є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДДДД  є
         є  +13 :    Номер функции (старший)   ¦ X ¦       ¦   ¦        є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДД  є
         є  +14 :    Номер функции (младший)   ¦ X ¦       ¦   ¦        є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДД  є
         є  +15 :     Содержимое регистра SI   ¦ X ¦       ¦   ¦        є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДД  є
         є  +17 :     Содержимое регистра DI   ¦ X ¦       ¦   ¦        є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДД  є
         є  +19 :  Адрес блока IOCTL запроса   ¦ X ¦       ¦   ¦        є
         є  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДДДД  є
         є                                                              є
         ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

            Название "Группа команд управления вводом/выводом"  не  совсем
         точно,  так  как эта команда используется при поддержке драйвером
         дополнительных возможностей.  Одна группа дополнительных  функций
         (доступ к которой обеспечивается IOCTL подфункцией 0CH) поддержи-
         вает  возможность  переключения  кодовых   страниц   (code   page
         switching),  средства  для  оперативной  реконфигурации драйвера.
         Другая большая группа функций (доступ  к  которой  обеспечивается
         IOCTL подфункцией 0DH) обеспечивает стандартный интерфейс для ап-
         паратурозависимых операций блоковых драйверов. Операции, входящие
         в данную группу, включают чтение, запись, верификацию, форматиро-
         вание целых дорожек,  чтение и модификацию блока параметров  BIOS
         (BPB).
            Расширенные возможности команды GENERIC IOCTL хорошо описаны в
         "MS-DOS Technical Reference Manual" ("MS-DOS.  Техническое описа-
         ние") в разделе,  описывающем функцию 44H MS-DOS.  В связи с тем,
         что эти функции предназначены в основном для поддержки оборудова-
         ния производителей, мы отсылаем читателей к упомянутому руководс-
         тву для получения более подробной информации.
            Команды GETLOGICALDEVICE и SETLOGICALDEVICE (23 и 24) являются
         дополнительными командами для блоковых драйверов и поддерживаются
         в MS-DOS начиная с версии 3.20.  Использование этих команд разре-
         шается  при наличии у драйвера атрибута GIOCTL (бит 6 слова атри-
         бутов) равного 1.

                                      - 6-46 -
                         Команды GET & SET LOGICAL DEVICE
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         ЙНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»
         є                                                                є
         єКоманда GET & SET LOGICAL DEVICE (23,24)                        є
         є                                                                є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ЪДДД¬                  є
         є+00 : 21                   Длина         ¦ X ¦ Блок. драйверы   є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         АДДДЩ                  є
         є+01 : номер #         Устройство         ЪДДД¬                  є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД         ¦   ¦ Симв. драйверы   є
         є+02 : команда            Команда         АДДДЩ                  є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД                                є
         є+03 :                     Статус     ЪДДДДДДДДДДДДДДДДДДДДДДДДД¬є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД     ¦ КОМАНДЫ :               ¦є
         є                 Зарезервировано     ¦                         ¦є
         є                                     ¦ 23:  GET LOGICAL DEVICE ¦є
         є                                     ¦ 24:  SET LOGICAL DEVICE ¦є
         є                                     АДДДДДДДДДДДДДДДДДДДДДДДДДЩє
         є                                                                є
         є                                          ЧТЕНИЕ      ЗАПИСЬ    є
         є                                                                є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ЪДДД¬ ДДДДД ЪДДД¬ ДДДДє
         є+13 :      Ввод (код устройства)          ¦ X ¦       ¦   ¦     є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДє
         є+14 :                Код команды          ¦ X ¦       ¦   ¦     є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДє
         є+15 :                     Статус          ¦ X ¦       ¦   ¦     є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД ГДДДґ ДДДДД ГДДДґ ДДДДє
         є+17 :            Зарезервировано          ¦   ¦       ¦ X ¦     є
         єДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД АДДДЩ ДДДДД АДДДЩ ДДДДє
         є                                                                є
         ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј
              Эти команды  используются  для  отслеживания  имени текущего
         диска для устройств, имеющих несколько логических дисков. Подобно
         команде GENERICIOCTL, команды GET/SET LOGICAL DEVICE доступны че-
         рез функцию 44H MS-DOS.  Подфункция 0H применяется для  получения
         текущего  имени  логического диска,  а функция 0FH для присвоения
         имени  нового  логического  диска.   Так   же   как   и   команда
         GENERICIOCTL, команды GET/SET LOGICAL DEVICE предназначены, в ос-
         новном, для поддержки устройств производителя (например, в случае
         драйвера  DRIVER.SYS,  для поддержки 3.5-дюймовых гибких дисков).
         Полное описание этих  команд  можно  найти  в  "MS-DOS  Technical
         Reference Manual" в разделе, описывающем функцию MS-DOS 44H, куда
         мы Вас и отсылаем.

                  Создание загрузочного файла драйвера устройства

            Выше уже упоминалось, что программа драйвера устройства похожа
         на обычную .COM программу.  Это утверждение тем более истинно при
         использовании описываемого метода создания .SYS  файла  драйвера.
         Заметьте, что нет никаких причин, кроме соглашений, для использо-
         вания расширения .SYS в файлах драйверов -  допустимы любые  рас-
         ширения.  В листинге 6-4 представлен диалог с системой при созда-
         нии драйвера "DRIVER".  Этот файл ассемблируется и линкуется  как
         обычная программа, после чего преобразуется в двоичный .SYS файл.
         Отметим,что отсутствие стека для драйвера является нормальным яв-

                                      - 6-47 -
         лением,  так  как  драйвер при работе использует собственный стек
         MS-DOS.

            В примере,  приведенном в листинге 6-4, создается также выход-
         ной  .LST  файл  ассемблера и выходной .MAP файл редактора связей
         (линкера). Конечно же, .OBJ и .EXE файлы могут быть удалены после
         создания .SYS файла.

                 Листинг 6-4. Процесс создания простого драйвера
         -----------------------------------------------------------------
         C> masm driver,driver,driver;

         Microsoft Macro Assembler Version 4.00
         Copyright Microsoft Corp 1981, 1983, 1984, 1985.
         All rights reserved.

           45976 Bytes symbol space free

              0 Warning Errors
              0 severe  Errors

         C> link driver,driver,driver;

         Microsoft 8086 Object linker
         Version 3.00 Copyright Microsoft Corp 1983, 1984, 1985

         Warning: no stack segment

         C> exe2bin driver driver.sys

         -----------------------------------------------------------------

                            Отладка драйверов устройств

            После того,  как драйвер установлен в системе, он уже не может
         быть отлажен с помощью MS-DOS (из-за проблемы реентерабельности).
         Однако отлаживать драйверы необходимо, так как подобно практичес-
         ки всем программам трудно ожидать от драйвера  правильной  работы
         после  первого запуска.  К решению задачи отладки драйверов можно
         подойти с трех сторон.
            Во-первых, разрабатывайте  драйвер по технологии "сверху вниз"
         - заставьте  работать основную часть программы, а затем добавляй-
         те более сложные блоки. Не пытайтесь сделать в первую очередь об-
         работчики IOCTL. Процедурами, правильной работы которых Вы должны
         добиться в первую очередь,  являются программы СТРАТЕГИЙ и ПРЕРЫ-
         ВАНИЙ, а также процедура инициализации INIT. В блоковых драйверах
         Вы  должны также добиться правильной работы команды MEDIACHECK и,
         если только Вы не установили NONIBM бит в слове атрибутов, коман-
         ды BUILDBPB. С помощью такого набора функций Вы, конечно, не смо-
         жете выполнять операции ввода/вывода,  однако  MS-DOS  сможет  по
         крайней мере успешно загрузить этот драйвер.
            Другой подход,  который может помочь в отладке драйверов, зак-
         лючается в использовании функций BIOS для вывода информации,  оп-
         ределяющей текущее состояние драйвера.  Знание места, до которого
         дошел драйвер, прежде чем аварийно завершиться очень помогает при
         отладке. Если у Вас нет ROM-BIOS, на который можно положиться, Вы
         можете  встроить в драйвер различные подпрограммы вывода.  Напри-

                                      - 6-48 -
         мер, отлаживая драйвер RDISK (приведенный в  конце  этой  главы),
         авторы  встроили в драйвер средства вывода на дисплей идентифици-
         рующего символа для каждой обрабатываемой команды ("I" для ПРЕРЫ-
         ВАНИЙ,  "S" для СТРАТЕГИЙ, "i" для INIT и т.д.). Это представляло
         действительную помощь когда драйвер загружался и был  доступен  с
         помощью  прерываний прямого доступа к диску,  но "сваливался" при
         попытке чтения директория диска. Взаимодействие между драйвером и
         системой  может  быть  одной  из самых сложных проблем  и,  к не-
         счастью, обычно может быть отлажено только после загрузки драйве-
         ра.
            Если Вы решили добавить отладочные команды к  Вашему драйверу,
         знайте,  что это вероятнее всего увеличит требуемую глубину стека
         и Вам,  возможно, придется использовать в драйвере локальный стек
         (если, конечно, Вы еще не сделали этого).
            При тестировании отдельных частей драйвера нет никакой необхо-
         димости отлаживать их после его загрузки.  Если Вам не жалко вре-
         мени,  потраченного на написание простой тестовой программы, соз-
         дающей  блоки запросов и передающей их драйверу для обработки, то
         Вы сможете использовать обычную программу DEBUG для отладки  тес-
         товой программы и самого драйвера. Это позволит Вам довести драй-
         вер до состояния,  при котором он уже может быть загружен,  после
         чего  использовать  другие способы отладки для исправления остав-
         шихся ошибок.
            При разработке  драйверов всегда пользуйтесь копией системного
         диска.  Ошибка в драйвере может привести к тому,  что система  не
         будет  загружаться или к разрушению каких-либо значимых данных на
         диске. По этим причинам Вам следует всегда иметь копию системного
         диска.

                Отображение списка загруженных в системе драйверов

            Очень часто при отладке драйверов полезно знать какие конкрет-
         но драйверы загружены в данный момент. На этот случай мы приводим
         текст небольшой программы, названной SD (SHOW DRIVERS -  показать
         драйверы). Примерный вид выводимой этой программой информации по-
         казан в листинге 6-5.
            Большинство отображаемых драйверов являются стандартными драй-
         верами MS-DOS,  эа исключением верхнего  драйвера  CON-устройства
         (драйвера консоли), который является драйвером ANSYI.SYS, и верх-
         него блокового драйвера, который является Bernulli Box драйвером.
         Нижний  блоковый  драйвер  является стандартным MS-DOS драйвером,
         поддерживающем одновременно один жесткий диск и два гибких.
            Колонка Attrib  содержит  слова  атрибутов драйверов,  колонка
         Address содержит начальный адрес каждого драйвера (взятый из поля
         связи  предыдущего в списке драйвера) и колонки STRAT и INTRP со-
         держат смещения программ СТРАТЕГИЙ и ПРЕРЫВАНИЙ от начала драйве-
         ра.  Исходный текст программы SD на языке ассемблера  приведен  в
         листинге  6-6.  Заметьте,  что  в программе SD используются файлы
         DRIVER.INC (листинг 6-7),  STDMAC.INC (листинг A-7, приложение A)
         и программа BIN2HEX файла STDLIB.LIB (листинг A-8, приложение A).


                                      - 6-49 -
          Листинг 6-5. Пример цепочки драйверов, выводимый программой SD
     --------------------------------------------------------------------
     SD-ShowDriv, Version 1.00, Copyright 1988 Kevin Jaeger
     Device         Type   Units   Attrib    Address       STRAT   INTRP
     -------------------------------------------------------------------
     NUL            Char    01      8004    0000:1898       1418    141E
     CON            Char    01      8013    08A9:0000       00A2    00AD
     --------      Block    02      0000    083D:0000       00A7    00B2
     CON            Char    01      8013    0070:0160       00A7    00B2
     AUX            Char    01      8000    0070:01F1       00A7    00B8
     PRN            Char    01      A000    0070:02A0       00A7    00C7
     CLOCK$         Char    01      8008    0070:034A       00A7    00DC
     --------      Block    03      0800    0870:0416       00A7    00E2
     COM1           Char    01      8000    0070:0203       00A7    00B8
     LPT1           Char    01      A000    0070:02B2       00A7    00C7
     LPT2           Char    01      A000    0070:0B13       00A7    00CD
     LPT3           Char    01      A000    0070:0B25       00A7    00D3
     COM2           Char    01      8000    0070:0B37       00A7    00BE
     <<< ------------------ End Of Driver List --------------------- >>>
     --------------------------------------------------------------------
                Листинг 6-6. Исходный текст программы SHOWDRIV.ASM
     ---------------------------------------------------------------------
     PAGE   60,132
     ; ************ SHOWDRIV *********************************************
     ;
     ; SHOWDRIV - Отображение списка загруженных драйверов MS-DOS
     ;
     ; ************ INCLUDES *********************************************
     ;
     INCLUDE        stdmac.inc
     INCLUDE        driver.inc
     ;
     ; ************ DGROUP (DATA) COMPONENT SEGMENTS *********************
     ;
     _DATA          SEGMENT BYTE PUBLIC 'DATA'
     _DATA          ENDS
     ;
     STACK          SEGMENT PARA STACK
                    dw      1024 dup (?)
     STACK          ENDS
     ;
     DGROUP         GROUP   _DATA, STACK
     ;
     ; ************ DATA STORAGE & TEMPLATES *****************************
     ;
     _DATA          SEGMENT BYTE PUBLIC 'data'
     ;
     ; параметры для поиска
     ;
     nuldev         db      'NUL     '              ; Имя NUL драйвера
     nulattr        dw      AT_CHR OR AT_NUL        ; Слово атрибутов
     ;
     ; Текстовые сообщения для вывода на дисплей. Формат :
     ;
     ; "Device         Type   Units   Attrib    Address     STRAT   INTRP"
     ; "-----------------------------------------------------------------"
     ; "xxxxxxxx       xxxx    xx      xxxx    xxxx:xxxx     xxxx    xxxx"

                                      - 6-50 -
     ; "<<< ------------------ End Of Driver List ------------------- >>>"
     ;
     $title db CR,LF
            db 'SD-ShowDriv, Version 1.00, Copyright 1988'
            db CR,LF,CR,LF
            db 'Device         Type   Units   Attrib    Address'
            db '       STRAT   INTRP'
            db CR,LF
            db '-----------------------------------------------'
            db '--------------------'
            db CR,LF,'$'
     $space db '    $'
     $block db '--------     Block    $'
     $char  db 'Char$'
     $colon db ':'
     $end   db CR,LF
            db '<<< ------------------ End Of Driver List -----'
            db '---------------- >>>'
     $crlf  db CR,LF,'$'
     ;
     ; Шаблон структуры
     ;
     devhead        STRUC                  ; Структура заголовка драйвера
            next    dd      ?              ; Указатель на следующего...
            attrib  dw      ?              ; Слово атрибутов
            strat   dw      ?              ; Смещение программы СТРАТЕГИЙ
            intrp   dw      ?              ; Смещение программы ПРЕРЫВАНИЙ
            dname   db      8 dup (?)      ; Имя/количество устройств
            term    db      ?              ; Конец заголовка драйвера
     devhead        ENDS
     ;
     _DATA          ENDS
     ;
     ; ************ ПРОГРАММА НАЧИНАЕТСЯ ЗДЕСЬ ***************************
     ;
     _TEXT  SEGMENT BYTE PUBLIC 'CODE'
            ASSUME  cs:_TEXT, ds:DGROUP, es:DGROUP, ss:DGROUP
     ;
            EXTRN   bin2hex:near           ; Шестнадцатиричный вывод
     main   PROC    FAR
            mov     ax,DGROUP              ; Установка сегмента данных
            mov     ds,ax
     ; Найдем NUL-драйвер с помощью поиска имени "NUL"
     ;
            cld
            mov     cx,0FFFEh              ; Счетчик для поиска
            xor     ax,ax                  ;
            mov     es,ax                  ; Начало поиска после таблицы
            mov     di,0400h               ;   прерываний
            mov     al,nuldev[rv]          ; Начинаем с поиска этой буквы
            ;;  mov al,[nuldev[rv]
     search:
            repne   scasb                  ; Ищем пока не найдем
            jne     exit                   ; Не нашли...
     ;
            push    cx                     ; Возможно нашли...
            push    di                     ; Сохраним текущую позицию

                                      - 6-51 -
            mov     si,offset nuldev+1     ; Остаток строки "NUL     "
            mov     cx,7                   ; Длина остатка
            repe    cmpsb                  ; Сравним остаток строки
            jne     not_it                 ; Не совпадают...
     ;
            sub     di,(offset term - offset attrib) ; Выровняем указатель
            cmpsw                            ; Это атрибут NUL-драйвера ?
            jne     not_it                   ; Нет...
            add     sp,4                     ; Удаляем сохраненные DI и CX
            sub     di,(offset strat - offset next) ; Выравниваем указатель
            jmp     found_nul                ; Нашли заголовок NUL-драйвера!
     ;
     not_it:                                 ; Восстанавливаем позицию
            pop     di                       ;   и счетчик
            pop     cx
            jmp     short search
     ;
     ; Нашли заголовок NUL-драйвера. Теперь выводим всю цепочку
     ;
     found_nul:
            @DisStr $title                   ; Выводим название (титул)
     show_driver:
            call    ShowDeviceInfo           ; Отобразим заголовок драйвера
            cmp     word ptr es:[di],-1      ; Проверим на конец цепочки
            je     done                      ; Если (-1) то на выход
            les     di,es:[di.next]          ; Если не (-1) то на следующий
            jmp     short show_driver        ;   заголовок
     done:
            @DisStr $end                     ; Завершающее сообщение
            @DisStr $crlf
     ;
     exit:  mov     al,0                     ; Нормальное завершение
            @ExitToDOS                       ; Завершение программы
     main   ENDP
     ;
     ; ************ ShowDeviceInfo ****************************************
     ; Подпрограмма ShowDeviceInfo отображает блок, адресуемый по ES:DI,
     ; предполагая что это заголовок драйвера. Формат выводимой информации
     ; показан выше.
     ;
     ShowDeviceInfo PROC    NEAR
            test    es,[di.attrib],AT_CHR  ; Драйвер символьный или
            jnz     is_char                ;   блоковый ?
            @DisStr $block                 ; Блоковый (без имени)
            xor     ah,ah
            mov     al,es:[di.dname]       ; Количество устройств
            jmp     short dis_units
     is_char:
            push    ds                     ; Сохраним DS
            push    es                     ; Выровняем сегменты
            pop     ds
            lea     si,es:[di.dname]       ; SI = смещение имени
            mov     cx,8                   ; Длина имени
     show_name:
            lodsb                          ; Выводим по одному символу
            @DisChr al                     ;   за раз
            loop    show_name

                                      - 6-52 -
            pop     ds                     ; Восстанавливаем DS
            @DisStr $space
            @DisStr $char                  ; Выводим тип драйвера
            @DisStr $space
            mov     ax,1                   ; Только одно устройство
     ;
     dis_units:
            mov     ch,02                  ; Выводим количество устройств
            call    bin2hex
            @DisStr $space
            mov     ch,04                  ; Вывод числовых данных
            mov     ax,es:[di.attrib]
            call    bin2hex                ; Выводим слово атрибутов
            @DisStr $space
     ;
            mov     ax,es
            call    bin2hex                ; Выводим сегментный адрес
            @DisChr $colon
            mov     ax,di
            call    bin2hex                ; Выводим смещение
            @DisStr $space
     ;
            mov     ax,es:[di.strat]
            call    bin2hex                ; Выводим адрес СТРАТЕГИЙ
            @DisStr $space
     ;
            mov     ax,es:[di.intrp]
            call    bin2hex                ; Выводим адрес ПРЕРЫВАНИЙ
            @DisStr $crlf
     ;
            ret
     ShowDeviceInfo ENDP
     ;
     ; ************ КОНЕЦ ПРОГРАММЫ, КОНЕЦ ФАЙЛА ************************
     ;
     _TEXT  ENDS
            END     main

     ---------------------------------------------------------------------


                           Листинг 6-7. Файл DRIVER.INC
     ---------------------------------------------------------------------
     ; ************ DRIVER.INC *******************************************
     ;
     ; Driver.Inc : Содержит определения и константы для использования при
     ; ассемблировании драйверов MS-DOS.
     ;
     ; ************ ОПРЕДЕЛЕНИЕ КОНСТАНТ, ИСПОЛЬЗУЕМЫХ В ДРАЙВЕРАХ *******
     ;
     ; Определение битов слова атрибутов драйвера :
     AT_CHR         EQU     1000000000000000b      ; Символьное устройство
     AT_IOCTL       EQU     0100000000000000b      ; Поддержка IOCTL
     AT_BUSY        EQU     0010000000000000b      ; Поддержка OTB
     AT_NOIBM       EQU     0010000000000000b      ; Не IBM устройство
     AT_NET         EQU     0001000000000000b      ; Сетевое устройство
     AT_OCRM        EQU     0000100000000000b      ; Поддержка OCRM

                                      - 6-53 -
     AT_GIOCTL      EQU     0000000001000000b      ; Поддержка GIOCTL
     AT_LOGICL      EQU     0000000001000000b      ; Get/Set Logical Dev
     AT_SPECL       EQU     0000000000010000b      ; Специальное устр-во
     AT_CLOCK       EQU     0000000000001000b      ; Устройство "ЧАСЫ"
     AT_NUL         EQU     0000000000000100b      ; Устройство NUL
     AT_STDOUT      EQU     0000000000000010b      ; Стандартные устр-ва
     AT_STDIN       EQU     0000000000000001b      ;   ввода и вывода
     ST_ERROR       EQU     1000000000000000b
     ST_BUSY        EQU     0000001000000000b
     ST_DONE        EQU     0000000100000000b
     ; Определение кодов ошибок драйверов устройств :
     WRITE_PROTECT          EQU      0
     UNKNOWN_UNIT           EQU      1
     NOT_READY              EQU      2
     UNKNOWN_UNIT           EQU      3
     CRC_ERROR              EQU      4
     BAD_REQUEST            EQU      5
     SEEK_ERROR             EQU      6
     UNKNOWN_MEDIA          EQU      7
     SECTOR_NOT_FOUND       EQU      8
     OUT_OF_PAPER           EQU      9
     WRITE_FAULT            EQU      0Ah
     READ_FAULT             EQU      0Bh
     GENERAL_FAILURE        EQU      0Ch
     INVALID_DISK_CHANGE    EQU      0Fh
     ; Статус, возвращаемый командой MEDIA CHECK :
     IsChanged      EQU     -1       ; носитель был заменен
     DontKnow       EQU     0        ; не известно была ли замена
     NotChanged     EQU     1        ; носитель был заменен
     ;
     ; ************ КОНЕЦ ФАЙЛА : DRIVER.INC *****************************
     ---------------------------------------------------------------------


                        Пример драйвера виртуального диска

            В конце этой главы, в листинге 6-10, мы приводим пример весьма
         упрощенного драйвера RAM-диска (т.е. драйвера виртуального диска,
         размещаемого в ОЗУ).  Несмотря на свою простоту,  драйвер на 100%
         работоспособен и может быть использован на любой  MS-DOS  системе
         начиная с версии 2.0 и выше. Драйвер RAM-диска, показанный в лис-
         тинге 6-10,  использует 360 Kбайт системной памяти  для  эмуляции
         стандартного пятидюймового дисковода.  Если Вы намерены использо-
         вать этот драйвер,  то Ваша система должна иметь по крайней  мере
         512 Kбайт памяти. Если Вы имеете меньше памяти или просто желаете
         иметь виртуальный диск меньших размеров,  то Вы  можете  изменить
         принимаемые  по  умолчанию  параметры,  которые  описаны в секции
         драйвера, помеченной как "Описание RAM-диска".
            Более элегантным решением изменения размеров RAM-диска являет-
         ся использование параметров командной строки.  Вспомните, что при
         входе  в  обработчик  команды  INIT  параметры  request.bpbtabo и
         request.bpbtabs содержат длинный указатель на  командную   строку
         драйвера. Эта строка может быть проверена на наличие переключате-
         лей и опций,  которые могут быть  использованы  для  конфигурации
         драйвера.  При  использовании  этого метода процедура INIT должна
         выполнить проверку,  скорректировать параметры в BPB и сегментный
         адрес завершения драйвера.

                                      - 6-54 -
            После того, как программа была обработана ассемблером и редак-
         тором связей,  переименуйте ее в RDISK.SYS.  Теперь создайте файл
         CONFIG.SYS (если,  конечно,  он еже не создан) и добавьте в  него
         командную строку :

                DEVICE=RDISK.SYS

            При первой  же перезагрузке драйвер будет установлен как драй-
         вер следующего по порядку дисковода (вероятно как драйвер  диско-
         вода C:,  если у Вас нет жесткого диска).  Ничего более для уста-
         новки драйвера RDISK не требуется.
            Доступ к RAM-диску возможен с помощью любых функций MS-DOS или
         программ,  за исключением команд DISKCOPY  и  DISKCOMP.  Обе  эти
         программы  ожидают  определенные  типы  дисков  и  не  работают с
         RAM-дисками.
            Драйвер RDISK,  приведенный в листинге 6-10,  содержит простой
         код, который может быть использован для отладки или  исследования
         драйверов. Он написан с использованием функций ввода/вывода уров-
         ня BIOS,  приведенных в листинге 6-8.  Для того, чтобы отладочный
         код располагался до адреса завершения драйвера,  RDISK включает в
         себя исходный текст файла BIOSIO.ASM (см.листинг  6-9).  Так  как
         библиотечные процедуры обычно добавляются редактором связей в ко-
         нец программы, их использование в драйверах устройств представля-
         ется проблематичным.
            Отладочный код может быть задействован путем включения  в файл
         RDISK оператора DEBUG EQU 1 или, при использовании Microsoft MASM
         версии 4 или более поздней,  указанием в командной  строке  опции
         /DDEBUG.
            Во время выполнения отладочный код использует ряд команд драй-
         вера в качестве индекса в таблице message_table.  Элементами таб-
         лицы message_table являются адреса  строк,  представляющих  имена
         команд,  находящихся  в  области  данных,  предшествующей таблице
         message_table.Эти текстовые строки отображаются с помощью аппара-
         турозависимой  процедуры  _biosprt.  В  драйвере  RDISK процедура
         _biosprt использует адаптер EGA с цветным монитором, что позволя-
         ет легко отличать отладочный текст от обычных сообщений MS-DOS.


                           Листинг 6-8. Файл BIOSIO.INC
     ----------------------------------------------------------------------
     ; ************ BIOSIO.INC ********************************************
     ;
     ; BiosIO.Inc содержит константы для использования процедур BIOS уровня
     ; находящихся в файле STDLIB.LIB
     ;
     ; Макрокоманда @Video для использования с видеопроцедурами
     ;
     @Video MACRO   function
            mov     ah,function
            int     10h
            ENDM
     ;
     ; ************ BIOS I/O Equates **************************************
     ;
     ; Эти определения поддерживают использование ввода/вывода уровня BIOS.
     ;
     ; Определения функций видеосервиса BIOS (INT 10H)

                                      - 6-55 -
     SET_CURSOR_POS EQU 02H ;; BH = страница, DH = строка, DL = колонка
     GET_CURSOR_POS EQU 03H ;; BH = страница; строка => DH, колонка => DL
     SET_PAGE       EQU 05H ;; AL => страница
     SCROLL_UP      EQU 06H ;; AL = #строк, BH => атрибут, C(x) = верхняя
     SCROLL_DOWN    EQU 07H ;;    левая, D(x) = нижняя правая,
                            ;;    (x)H = строка, (x)L = колонка
     READ_CHR_ATR   EQU 08H ;; BH = страница; атр. => AH, симв. => AL
     WRITE_CHR_ATR  EQU 09H ;; BH = страница, CX = 1, AL = симв., BL = атр.
     WRITE_CHAR     EQU 0AH ;; BH = страница, CX = 1, AL = симв., без атр.
     WRITE_TEXT     EQU 0EH ;; BH = страница, AL = символ
     GET_MODE       EQU 0FH ;; режим => AL, #колонок => AH, страница => BH
     ;
     ; Атрибуты символов при использовании адаптера EGA
     BLINK          EQU     10000000b
     BRIGHT         EQU     00001000b
     BLACK_F        EQU     00h
     BLUE_F         EQU     01h
     GREEN_F        EQU     02h
     CYAN_F         EQU     03h
     RED_F          EQU     04h
     MAGENTA_F      EQU     05h
     YELLOW_F       EQU     06h
     WHITE_F        EQU     07h
     BLACK_B        EQU     00h
     BLUE_B         EQU     10h
     GREEN_B        EQU     20h
     CYAN_B         EQU     30h
     RED_B          EQU     40h
     MAGENTA_B      EQU     50h
     YELLOW_B       EQU     60h
     WHITE_B        EQU     70h
     ;
     ; ************ КОНЕЦ ФАЙЛА BIOSIO.INC ********************************


                           Листинг 6-9. Файл BIOSIO.ASM
     ----------------------------------------------------------------------
     PAGE   60,132
     PUBLIC _biosprt
     ; ************ BIOSIO.ASM ********************************************
     ; BIOSIO: Содержит процедуры для выполнения ввода/вывода на
     ; уровне BIOS, используя стандартные вызовы BIOS. Эти процедуры
     ; предназначены для целей отладки.
     ;
     IFNDEF DEBUG           ; если не часть DEBUG, то должна быть часть
                            ; от LIBRARY, и должна включать наши
                            ; собственные определения
     ; ************ INCLUDES **********************************************
     ;
     INCLUD biosio.inc       ; BIOS I/O difinition
     ;
     ; ************ DGROUP (DATA) COMPONENT SEGMENTS **********************
     _DATA   SEGMENT BYTE PUBLIC 'DATA'
     _DATA   ENDS
     ;
     DGROUP  GROUP   _DATA
     ;

                                      - 6-56 -
     ;************* PROGRAM CODE STARTS HERE ******************************
     ;
     _TEXT   SEGMENT BYTE PUBLIC 'CODE'
             ASSUME  cs:_TEXT, ds:DGROUP, es:DGROUP, ss:DGROUP
     ENDIF
     ;
     ; Шаблон структуры, описывающей состояние стека для _BIODPRT
     bpframe STRUC
                    dw      ?                ; Старый BP
                    dw      ?                ; адрес возврата
             p1     dw      ?                ; параметр #1
             p2     dw      ?                ; параметр #2
             p3     dw      ?                ; параметр #3
             p4     dw      ?                ; параметр #4
     bpframe ENDS
     prtbase EQU    [bp]
     ;
     ; _BIOSPRT
     ; Эта подпрограмма выполняет вывод на экран на уровне BIOS и
     ; используется для отладки драйвера. Подпрограмма использует
     ; видеорежим 03h : 80*25 цветной текст
     ;
     ; Эквивалентный языку Си синтаксис вызова : biosprt(string,color)
     ;
     _biosprt        PROC    NEAR
             push    bp
             mov     bp,sp
             push    si
             push    cx
             push    bx
     ;
             @Video  GET_MODE                 ; Получить номер тек.страницы
             mov     si,word prt [prtbase.p1] ; адрес строки
             mov     bl,byte prt [prtbase.p2] ; атрибут
             mov     cx,1
     ;
     biosprtloop:
             lodsb                           ; Берем очередной символ
             or      al,al                   ; Строка завершается нулем
             jz      biosprtdone
             cmp     al,'$'                  ; или завершается "$"
             jz      biosprtdone
             push    ax
             mov     al,020h
             @Video  WRITE_CHR_ATR           ; Пробел с атрибутом
             pop     ax
             @Video  WRITE_TEXT              ; Символ в режиме TTY
             jmp     biosprtloop             ; Следующий символ
     ;
     biosprtdone
             pop     bx
             pop     cx
             pop     si
             pop     bp
             ret
     _boisprt        ENDP
     ;

                                      - 6-57 -
     IFNDEF  DEBUG                           ; если не включено как часть
     _TEXT   ENDS                            ; DEBUG, то потребуются наши
     ENDIF                                   ; собственные ENDS
     ;
     ; ************ КОНЕЦ ФАЙЛА BIOSIO.ASM ********************************
     ;       END            ; При использовании в библиотеке, удалите ";"

     ----------------------------------------------------------------------

                  Листинг 6-10. Исходный текст драйвера RAM-диска
     ----------------------------------------------------------------------
     PAGE 60,132
     ; ************ RDISK.ASM : MS-DOS ДРАЙВЕР RAM-ДИСКА ******************
     ;
     ; Этот файл содержит исходный текст простого MS-DOS драйвера RAM-диска
     ; эмулирующего 360K флоппи-диск.
     ;
     ; В этом примере демонстрируются основные принципы построения драйвера
     ; устройств, включая один из методов, который можно использовать для
     ; отладки драйверов. Для установки этого драйвера включите в файл
     ; CONFIG.SYS строку "DEVICE=RDISK.SYS"
     ;
     ; ============ ВСПОМОГАТЕЛЬНЫЕ ФАЙЛЫ ДЛЯ ДРАЙВЕРА ====================
     ;
     INCLUDE driver.inc                     ; Константы для MS-DOS драйвера
     IFDEF   DEBUG
     INCLUDE biosio.inc                     ; Определения для отладки
     ENDIF
     ;
     ; ============ КОНСТАНТЫ =============================================
     ;
     ; Ограничения,накладываемые версией MS-DOS на максимальный код команды
     ;
     CMD_PRE_30               EQU      00Ch  ;  до MS-DOS версии 3.00
     CMD_PRE_32               EQU      00Fh  ;  до MS-DOS версии 3.20
     CMD_32                   EQU      018h  ;  начиная с версии 3.20
     ;
     IFDEF           DEBUG
     CR                       EQU      0Ah   ; используются в отладочных
     LF                       EQU      0Dh   ;   сообщениях
     ENDIF
     ;
     PAGE
     ;
     ; ============ ШАБЛОНЫ СТРУКТУР ======================================
     ;
     request         EQU      es:[di.rv]     ; указатель на блок запроса
     ; request       EQU      es:[di[rv]
     ; Структура заголовка запроса
     ;
     reqhdr          STRUC
                     rlength  db      ?              ; размер блока запроса
                     unit     db      ?              ; номер устройства
                     command  db      ?              ; код команды
                     status   dw      ?              ; возвращаемый статус
                              db      8 DUP (?)      ; зарезервировано
     reghdr          ENDS

                                      - 6-58 -
     ;
     ; Структура блока запроса для команды INIT
     ;
     inithdr         STRUC
                              db      (type reqhdr) DUP (?)
                     units    db      ?              ; количество устройств
                     endadro  dw      ?              ; смещение и сегмент
                     endadrs  dw      ?              ;   адреса завершения
                     bpbtabo  dw      ?              ; смещение и сегмент
                     bpbtabs  dw      ?              ;   таблицы BPB
                     devnum   db      ?              ; номер устройства
     inithdr         ENDS
     ;
     ; Структура блока запроса для команды MEDIA CHECK
     ;
     mchkhdr         STRUC
                              db       (type reqhdr) DUP (?)
                     mbd      db      ?           ; описатель носителя
                     chande   dw      ?           ; статус замены
                     volume   dd      ?           ; указатель на имя тома
     mchkhdr         ENDS
     ;
     ; Структура блока запроса для команды BUILD BPB
     ;
     bpbhdr          STRUC
                              db       (type reqhdr) DUP (?)
                              db      ?              ; описатель носителя
                              dd      ?              ; указатель на FAT
                     bpbptro  dw      ?              ; смещение BPB
                     bpbptrs  dw      ?              ; сегмент BPB
     bpbhdr          ENDS
     ;
     ; Структура блока запроса для команд чтения/записи
     ;
     iohdr           STRUC
                              db       (type reqhdr) DUP(?)
                              db      ?             ; описатель носителя
                     bufprt   dd      ?             ; адрес буфера
                     count    dw      ?             ; кол-во байт/секторов
                     start    dw      ?             ; # начального сектора
                     nuvol    dd      ?             ; адрес нов. имени тома
     iohdr           ENDS
     ;
     ; Структура блока параметров BIOS (BPB)
     ;
     bpbstrc         STRUC
                     bps      dw      ?      ; количество байтов в секторе
                     spau     db      ?      ; кол-во секторов в кластере
                     nrs      dw      ?      ; кол-во зарезервир. секторов
                     nft      db      ?      ; количество копий FAT
                     nde      dw      ?      ; кол-во элементов директория
                     nls      dw      ?      ; кол-во логических секторов
                     md       db      ?      ; байт описателя носителя
                     nfs      dw      ?      ; размер FAT в секторах
     bpbstrc         ENDS
     ;

                                      - 6-59 -
     PAGE
     ;
     ; ============= НАЧАЛО КОДА ДРАЙВЕРА =================================
     ;
     _TEXT           SEGMENT  BYTE    PUBLIC 'CODE'
                     ASSUME   CS:_TEXT, DS:_TEXT, ES:NOTHING
                     ORG      0
     ORIGIN          EQU      $
     ;
     ; ============= ЗАГОЛОВОК ДРАЙВЕРА ===================================
     ;
                     dw       -1,-1          ; указатель на след. драйвер
                     dw       AT_IOCTL OR AT_OCRM OR AT_NET
                     dw       offset STRATEGY       ; смещение СТРАТЕГИЙ
                     dw       offset INTERRUPT       ; смещение ПРЕРЫВАНИЙ
                     db       1,'CDEVICE..           ; кол-во устройств/имя
     ;
     ; ============= ТАБЛИЦА АДРЕСОВ ОБРАБОТЧИКОВ КОМАНД ==================
     ;
     JUMPTAB        LABEL   WORD
                    dw      offset INIT             ; 0 - инициализация
                    dw      offset MEDIA_CHECK      ; 1 - проверка носителя
                    dw      offset BUILD_BPB        ; 2 - построить BPB
                    dw      offset IOCTL_INPUT      ; 3 - IOCTL ввод
                    dw      offset READ             ; 4 - ввод из устр-ва
                    dw      offset READ_NOWAIT      ; 5 - неразруш. ввод
                    dw      offset INPUT_STATUS     ; 6 - ввод статуса
                    dw      offset INPUT_FLUSH      ; 7 - сбросить ввод
                    dw      offset WRITE            ; 8 - вывод на устр-во
                    dw      offset WRITE_VERIFY     ; 9 - вывод с проверкой
                    dw      offset OUTPUT_STATUS    ; A - вывод статуса
                    dw      offset OUTPUT_FLUSH     ; B - сбросить вывод
                    dw      offset IOCTL_OUTPUT     ; C - вывод IOCTL
                    dw      offset DEVICE_OPEN      ; D - открыть устр-во
                    dw      offset DEVICE_CLOSE     ; E - закрыть устр-во
                    dw      offset REMOVABLE        ; F - носитель сменный?
                    dw      offset NO_COMMAND       ; 10
                    dw      offset NO_COMMAND       ; 11
                    dw      offset NO_COMMAND       ; 12
                    dw      offset GENERIC_IOCTL    ; 13 - Generic IOCTL
                    dw      offset NO_COMMAND       ; 14
                    dw      offset NO_COMMAND       ; 15
                    dw      offset NO_COMMAND       ; 16
                    dw      offset GET_LOGICAL      ; 17 - получить/устано-
                    dw      offset SET_LOGICAL      ; 18 - вить лог.устр-во
     ;
     ; ============ ОБЛАСТЬ ДАННЫХ ДРАЙВЕРА ===============================
     ;
     reg_ptr        dd       ?               ; адрес блока запроса
     max_cmd        db       CMD_PRE_30      ; максимально допустимый код
     ;                                       ;   команды
     save_ss        dw       ?               ; значение SS на входе
     save_sp        dw       ?               ; значение SP на входе
     ;

                                      - 6-60 -
     PAGE
     ;
     ; ============ ПРОГРАММА СТРАТЕГИЙ ====================================
     ;
     STRATEGY                PROC    FAR
                    mov     cs:word ptr [reg_ptr],bx
                    mov     cs:word ptr [reg_ptr+2],es
                    ret
     strategy               ENDP
     ;
     ; ============ ПРОГРАММА ПРЕРЫВАНИЙ ===================================
     ;
     INTERRUPT               PROC     FAR
                    push    ax               ; сохранить все рабочие
                    push    cx               ;   регистры
                    push    dx
                    push    bx
                    push    bp
                    push    si
                    push    di
                    push    ds
                    push    es
     ;
                    push    cs               ; определим локальный сегмент
                    pop     ds               ;   данных
     ;
                    mov     word ptr save_ss,ss      ; сохраним входное
                    mov     word ptr save_sp,sp      ; значение SS и SP
     ;
                    mov     bx,cs                    ; установим локальный
                    mov     ax,offset local_stack - 2        ; стек
                    mov     ss,bx
                    mov     sp,ax
     ;
                    les     di,[req_ptr]             ; получить адрес блока
                    mov     bl,request.command       ; запроса и команду
     ;
     ; установим заранее код ошибки на случай если команда неверная
     ;
                    mov     ax,(ST_ERROR OR UNKNOWN_COMMAND)
                    cmp     bl,[max_cmd]     ; команда поддерживается ?
                    ja      exit             ; нет - отвергаем ее
     ;
     ; Выдаем указанную команду на выполнение соответствующему обработчику.
     ; Каждый обработчик получает управление с CS и DS установленными на
     ; сегмент драйвера и ES:DI указывающем на блок запроса. Свой статус
     ; обработчики возвращают в регистре AX.
     ;
                    xor     bh,bh            ; BX - индекс в таблице
                    shl     bx,1             ;   команд
     IFDEF          DEBUG
                    call    print_command    ; выдаем имя обрабатываемой
     ENDIF                                   ;   команды
                    call    word ptr jumptab[bx]     ; вызываем обработчик
     ;
     ; Перешлем статус из регистра AX в слово состояния блока запроса
     ;
     exit:          push    cs               ; установка локального

                                      - 6-61 -
                    pop     ds               ;   сегмента данных
     ;
                    les     di,[req_ptr]     ; получим адрес блока запроса
                    or      ax,ST_DONE       ; установим бит DONE
                    mov     request.status,ax        ; сохраним статус
     ;
                    mov     ss,word ptr save_ss      ; восстановим значение
                    mov     sp,word ptr save_sp      ;   регистров SS:SP
     ;
                    pop     es               ; восстановим содержимое
                    pop     ds               ;   регистров
                    pop     di
                    pop     si
                    pop     bp
                    pop     bx
                    pop     dx
                    pop     cx
                    pop     ax
                    ret
     interrupt      ENDP
     ;
     PAGE
     ;
     ; ============ ОБРАБОТЧИКИ КОМАНД ====================================
     ;
     NO_COMAND      PROC    NEAR     ; неподдерживаемая команда
            ret                      ; возврат с ошибкой
     NO_COMMAND     ENDP
     ;
     MEDIA_CHECK    PROC    NEAR     ; 1 - проверка носителя
            mov     request.change,NotChanged
            xor     ax,ax
            ret
     MEDIA_CHECK    ENDP
     ;
     BUILD_BPB      PROC    NEAR     ; 2 - построить BPB
            mov     request.bpbptro,offset bpb
            mov     request.bpbptrs,cs
            xor     ax,ax
            ret
     BUILD_BPB      ENDP
     ;
     IOCTL_INPUT    PROC    NEAR     ; 3 - ввод IOCTL
            xor     ax,ax
            ret
     IOCTL_INPUT    ENDP
     ;
     READ           PROC    NEAR     ; 4 - ввод из устройства
            call    verify           ; проверка и установка параметров
            jc      rd_err           ; выход по ошибке
            les     di,request.bufptr   ; считываем в буфер
            rep     movsw            ; передача
            xor     ax,ax            ; нет ошибок
     rd_err:
            ret
     READ           ENDP
     ;

                                      - 6-62 -
     READ_NOWAIT    PROC    NEAR     ; 5 - неразрушающий ввод
            xor     ax,ax            ;     без ожидания
            ret
     READ_NOWAIT    ENDP
     ;
     INPUT_STATUS   PROC    NEAR     ; 6 - ввод статуса
            xor     ax,ax
            ret
     INPUT_STATUS   ENDP
     ;
     INPUT_FLUSH    PROC    NEAR     ; 7 - сбросить входную очередь
            xor     ax,ax
            ret
     INPUT_FLUSH    ENDP
     ;
     WRITE          PROC    NEAR     ; 8 - вывод на устройство
            call    verify           ; проверка и установка параметров
            jc      wr_err           ; выход при ошибке
            push    ds               ; сохраним сегмент "сектора"
            lds     si,request.bufptr   ; записываем из буфера
            pop     es               ; на диск
            xor     di,di            ; с нулевым смещением
            rep     movsw            ; передача
            xor     ax,ax            ; нет ошибок
     wr_err:
            ret
     WRITE          ENDP
     ;
     WRITE_VERIFY   PROC    NEAR     ; 9 - вывод с проверкой
            call    write
            ret
     WRITE_VERIFY   ENDP
     ;
     OUTPUT_STATUS  PROC    NEAR     ; A - вывод статуса
            xor     ax,ax
            ret
     OUTPUT_STATUS  ENDP
     ;
     OUTPUT_FLUSH   PROC    NEAR     ; B - сбросить выходную очередь
            xor     ax,ax
            ret
     OUTPUT_FLUSH   ENDP
     ;
     IOCTL_OUTPUT   PROC    NEAR     ; C - вывод IOCTL
            xor     ax,ax
            ret
     IOCTL_OUTPUT   ENDP
     ;
     DEVICE_OPEN    PROC    NEAR     ; D - открыть устройство
            xor     ax,ax
            ret
     DEVICE_OPEN    ENDP
     ;
     DEVICE_CLOSE   PROC    NEAR     ; E - закрыть устройство
            xor     ax,ax
            ret
     DEVICE_CLOSE   ENDP

                                      - 6-63 -
     ;
     REMOVABLE      PROC    NEAR     ; F - носитель сменный ?
            mov     ax,ST_BUSY       ; нет !
            ret
     REMOVABLE      ENDP
     ;
     GENERIC_IOCTL  PROC    NEAR     ; 13 - групповой IOCTL запрос
            xor     ax,ax
            ret
     GENERIC_IOCTL  ENDP
     ;
     GET_LOGICAL    PROC    NEAR     ; 17 - получить имя логического
            xor     ax,ax            ;      диска
            ret
     GET_LOGICAL    ENDP
     ;
     SET_LOGICAL    PROC    NEAR     ; 18 - установить имя логического
            xor     ax,ax            ;      диска
            ret
     SET_LOGICAL    ENDP
     ;
     PAGE
     ; ------------ Подпрограммы обработки запросов -----------------------
     ; Эти подпрограммы вызываются для обработки параметров любого запроса
     ; на ввод/вывод.
     ; На входе :
     ;    ES:DI - содержит адрес блока запроса
     ; Действия :
     ;    Проверка параметра "номер сектора" на допустимость.
     ;    Преобразование этого параметра в "сегмент:смещение".
     ;    Выровнять счетчик для предотвращения "перекрытия".
     ; На выходе :
     ;    DS:SI - содержит адрес "сектора" в RAM-диске
     ;    ES:DI - содержит адрес блока запроса
     ;    CX - содержит количество передаваемых слов.
     ;
     verify PROC    NEAR
     ; проверим,что номера начального и конечного секторов лежат в пределах
     ; от 0 до N.
            mov     cx,request.start         ; сравним номер начального
            cmp     cx,bpb.nls               ;   сектора с количеством
            jae     out_of_range             ;   логических секторов
            add     cx,request.count         ; найдем номер конечного
            dec     cx                       ;   сектора и тоже сравним
            cmp     cx,bpb.nls               ; если номера секторов
            jb      in_range                 ;   нормальные то продолжим
     ; заданные секторы не содержатся на диске
     out_of_range:
            mov     ax,ST_ERROR OR SECTOR_NOT_FOUND
            mov     request.count,0          ; ничего не было передано
            stc                              ; возвращаемся с ошибкой
            ret
     ; вычислим сегментный адрес начального сектора
     in_range:
            mov     ax,bpb.bps               ; количество байт в секторе
            mov     cl,4                     ; разделим на 16 для получения
            shr     ax,cl                    ;   размера в параграфах

                                      - 6-64 -
            mul     request.start            ; смещение параграфа относи-
                                             ;   тельно начала диска
            add     ax,RPARA                 ; смещение параграфа относи-
            mov     dx,cs                    ;   тельно CS
            add     ax,dx                    ; абсолютное смещ. параграфа
            mov     si,ax                    ; сохраним сегмент в SI
     ; вычислим и проверим счетчик передаваемых данных
            mov     ax,bpb.bps               ; размер сектора в байтах
            mul     request.count            ; счетчик передачи в байтах
            cmp     dx,0                     ; проверим на корректность
            jne     out_of_range
     ; выровняем счетчик в AX для предотвращения перекрытия
            mov     cx,word ptr request.bufptr
            cmp     cx,0                     ; смещение = 0
            je      set_size
            neg     cx                       ; остаток = 64K - смещение
            cmp     cx,ax                    ;   буфера
            jae     set_size                 ; если остаток меньше счетчика,
            mov     ax,cx                    ;   то передаем только остаток
     ; установим количество передаваемых секторов и счетчик передачи
     set_size:
            mov     cx,ax                    ; счетчик передачи в байтах
            shr     cx,1                     ; преобразуем в счетчик слов
            div     bpb.bps                  ; (DX был 0) кол-во секторов
            mov     request.count,ax         ; сохраним счетчик передачи
     ; загрузим в DS:SI адрес блока в памяти
            mov     ds,si
            xor     si,si
     ; установим направление передачи и вернемся без ошибок
            cld
            clc
            ret
     verify ENDP
     ;
     IFDEF  DEBUG
     INCLUDE        biosio.asm
     PAGE
     ;
     ; ************ КОД И ДАННЫЕ ДЛЯ ОТЛАДКИ ******************************
     ;
     ; Отладочные сообщения
     ;
     NO_COMMAND_msg    db   'NO COMMAND',CR,LF,'$'
     INIT_msg          db   'INITialization',CR,LF,'$'
     MEDIA_CHECK_msg   db   'MEDIA Check',CR,LF,'$'
     BUILD_BPB_msg     db   'Build BIOS Parameter Block',CR,LF,'$'
     IOCTL_INPUT_msg   db   'IO Control Input',CR,LF,'$'
     READ_msg          db   'Input from Device',CR,LF,'$'
     READ_NOWAIT_msg   db   'Nondestructive Input no-wait',CR,LF,'$'
     INPUT_STATUS_msg  db   'Input Status',CR,LF,'$'
     INPUT_FLUSH_msg   db   'Flush Input Queue',CR,LF,'$'
     WRITE_msg         db   'Output to Device',CR,LF,'$'
     WRITE_VERIFY_msg  db   'Output with Verify',CR,LF,'$'
     OUTPUT_STATUS_msg db   'Output Status',CR,LF,'$'
     OUTPUT_FLUSH_msg  db   'Flush Output Queue',CR,LF,'$'
     IOCTL_OUTPUT_msg  db   'IO Control Output',CR,LF,'$'
     DEVICE_OPEN_msg   db   'Open a Device',CR,LF,'$'

                                      - 6-65 -
     DEVICE_CLOSE_msg  db   'Close a Device',CR,LF,'$'
     REMOVABLE_msg     db   'Is Media Removable',CR,LF,'$'
     GENERIC_IOCTL_msg db   'Generic IOCTL Request',CR,LF,'$'
     GET_LOGICAL_msg   db   'Get Logical Device',CR,LF,'$'
     SET_LOGICAL_msg   db   'Set Logical Device',CR,LF,'$'
     ;
     PAGE
     ;
     ; ============= ТАБЛИЦА АДРЕСОВ ОТЛАДОЧНЫХ СООБЩЕНИЙ =================
     ;
     message_table  LABEL   WORD
            dw      offset INIT_msg          ; 01 - инициализация
            dw      offset MEDIA_CHECK_msg   ; 02 - проверка носителя
            dw      offset BUILD_BPB_msg     ; 03 - построить BPB
            dw      offset IOCTL_INPUT_msg   ; 04 - ввод IOCTL
            dw      offset READ_msg          ; 05 - ввод из устройства
            dw      offset READ_NOWAIT_msg   ; 06 - неразруш. ввод без ожид.
            dw      offset INPUT_STATUS_msg  ; 07 - ввод статуса
            dw      offset INPUT_FLUSH_msg   ; 08 - сброс входной очереди
            dw      offset WRITE_msg         ; 09 - вывод на устройство
            dw      offset WRITE_VERIFY_msg  ; 10 - вывод с проверкой
            dw      offset OUTPUT_STATUS_msg ; 11 - вывод статуса
            dw      offset OUTPUT_FLUSH_msg  ; 12 - сброс выходной очереди
            dw      offset IOCTL_OUTPUT_msg  ; 13 - вывод IOCTL
            dw      offset DEVICE_OPEN_msg   ; 14 - открыть устройство
            dw      offset DEVICE_CLOSE_msg  ; 15 - закрыть устройство
            dw      offset REMOVABLE_msg     ; 16 - носитель сменный ?
            dw      offset NO_COMMAND_msg    ; 17 -
            dw      offset NO_COMMAND_msg    ; 18 -
            dw      offset NO_COMMAND_msg    ; 19 -
            dw      offset GENERIC_IOCTL_msg ; 20 - групповой IOCTL запрос
            dw      offset NO_COMMAND_msg    ; 21 -
            dw      offset NO_COMMAND_msg    ; 22 -
            dw      offset NO_COMMAND_msg    ; 23 -
            dw      offset GET_LOGICAL_msg   ; 24 - получить имя диска
            dw      offset SET_LOGICAL_msg   ; 25 - установить имя диска
     ;
     PAGE
     ; PRINT_COMMAND
     ;
     ; Эта процедура вызывает функцию BIOS для печати (_biosprt), передавая
     ; ей адрес строки, содержащей имя только что вызванной команды. При
     ; вызове этой процедуры удвоенный код команды передается в регистре BX.
     ; Все используемые регистры сохраняются.
     ;
     print_command  PROC    NEAR
            push    ax                       ; сохраним содержимое рег. AX
            mov     ax, BLUE_F OR BRIGHT OR BLACK_B  ; установим цвет
            push    ax
            mov     ax,word ptr message_table[bx]    ; адрес строки
            push    ax
            call    _biosprt                 ; вызываем процедуру BIOS
            add     sp,4                     ; очищаем стек от параметров
            pop     ax                       ; восстанавливаем AX и выходим
            ret
     print_command  ENDP
     ENDIF

                                      - 6-66 -
     ;
     PAGE
     ;
     ; ******* ВНУТРЕННИЙ СТЕК И КОНЕЦ ОПЕРАЦИОННОЙ ЧАСТИ ДРАЙВЕРА ********
     ;
            db      32 DUP ('stack   ')      ; внутренний стек глубиной
     local_stack    EQU     $                ;   256 байт
     ;
     bpb_tab        dw      offset bpb       ; указатель на BPB
     ;
     LAST_USED      EQU     $                ; адрес завершения
     ;
     ; ******* ХАРАКТЕРИСТИКИ RAM-ДИСКА, ПРИНИМАЕМЫЕ ПО УМОЛЧАНИЮ **********
     ;
     ; Параметры для 5-1/4" двустороннего двойной плотности диска с девятью
     ; секторами на дорожке.
     ;
     MTYPE   EQU    0FDh             ; байт описателя носителя
     TRACKS  EQU    40               ; 40 дорожек
     SECTORS EQU    9                ; 9 секторов на дорожке
     DSIZE   EQU    512              ; 512 байт в секторе
     SIDES   EQU    2                ; 2 стороны на диске
     ;
     FSECS   EQU    2                ; количество секторов в FAT
     DIREN   EQU    112              ; количество элементов директория
     DSECS   EQU    7                ; 7 секторов в директории
     CLSIZ   EQU    2                ; 2 сектора в кластере
     ;
     STOTAL  EQU    TRACKS*SECTORS*SIDES     ; всего секторов
     PTOTAL  EQU    (DSIZE/16)*STOTAL        ; всего параграфов
     ;
     ; ************ НАЧАЛО ОБЛАСТИ ДАННЫХ RAM-ДИСКА ***********************
     ;
     ; RAM-диск д.б. выровнен на границу параграфа
     ;
            IF      ($-ORIGIN) mod 16
            ORG     ($-ORIGIN) + 16 - (($-ORIGIN) mod 16)
            ENDIF
     RDISK  LABEL   BYTE             ; начало RAM-диска
     RPARA  EQU     ($-ORIGIN)/16    ; размер кода в параграфах
     ;
     ; ------------ Блок параметров BIOS ----------------------------------
     ;
            jmp     near ptr boot    ;  JMP  (3 байта)
            db      'IBM  3.1'       ; 8 байт имя и версия
     ;
     bpb    bpbstrc <DSIZE,CLSIZ,1,2,DIREN,STOTAL,MTYPE,FSECS>
            dw      SECTORS          ; количество секторов на дорожке
            dw      SIDES            ; количество головок чтения/записи
            dw      0                ; количество скрытых секторов
     boot:
            db      (DSIZE-30) DUP (?)       ; остаток boot_sector
     ;
     ; ------------ Таблицы размещения файлов (FAT) -----------------------
     ;                                       ; первые два элемента FAT
     FAT_1  db      MTYPE,0FFh,0FFh          ; нулевой остаток FAT

                                      - 6-67 -
            db      (DSIZE-3) DUP (0)
            db      ((FSECS-1) * DSIZE) DUP (0)
     FAT_2  db      MTYPE,0FFh,0FFh          ; первые два элемента FAT
            db      (DSIZE-3) DUP (0)        ; нулевой остаток FAT
            db      ((FSECS-1) * DSIZE) DUP (0)
     ;
     ; ------------ Сектора директория ------------------------------------
     ;
     DIREC  db      'RAM_DISK   '            ; имя тома (11 байт)
            db      08h                      ; VID
            db      10 DUP (?)               ; зарезервировано
            dw      0600h                    ; время 12:00:00 (полдень)
            dw      021h                     ; дата 1 января 1980 года
            dw      0                        ; начальный кластер 0
            dd      0                        ; размер файла 0
            db      (DSIZE-32) DUP (0)       ; нулевой остаток директория
            db      ((DSECS-1) * DSIZE) DUP (0)
     BUFFER LABEL   BYTE                     ; начало области данных
     ;
     ; ************ ПРОЦЕДУРА ИНИЦИАЛИЗАЦИИ *******************************
     ;
     INCLUDE        stdmac.inc
     ;
     ; ============ Область данных инициализации ==========================
     ;
     $signon        db      'RAM DISK Driver Version 1.00 Installed: Drive'
     $desig         db      'A'
     $crlf          db      0Dh,0Ah,'$'
     ;
     ; ============ Начало процедуры инициализации ========================
     ;
     INIT   PROC    NEAR             ; 00 - инициализация
     ;
     ; установим адрес завершения, количество устройств и указатель на
     ; таблицу BPB
     ;
            mov     request.endadro,0        ; адрес конца драйвера
            mov     request.endadrs,cs
            add     request.endadrs,(RPARA+PTOTAL)   ; последний параграф
            mov     request.units,1
            mov     request.bpbtabo,offset bpb_tab
            mov     request.bpbtabs,cs
            mov     al,$desig                ; скорректируем имя диска
            add     al,request.devnum
            mov     $desig,al
     ;
     ; вывод на экран идентификационной строки
            @DisStr $signon
     ;
     ; скорректируем значение "max_cmd" исходя из версии MS-DOS
            @GetDOSVersion                   ; получим номер версии MS-DOS
            cmp     al,3                     ; MS-DOS версии 3.00 и выше ?
            jb      init_done             ; нет - прекращаем инициализацию
            mov     [max_cmd],CMD_PRE_32     ; команды для MS-DOS 3.00
            cmp     ah,2                     ; MS-DOS версии 3.20 и выше ?
            jb      init_done             ; нет - прекращаем инициализацию
            mov     [max_cmd],CMD_32         ; команды для MS-DOS 3.20

                                      - 6-68 -
     ;
     init_done:
            xor     ax,ax                    ; нет проблем !
            ret
     INIT   ENDP
     ;
     ; ************ КОНЕЦ ДРАЙВЕРА. КОНЕЦ ФАЙЛА ***************************
     ;
     _TEXT  ENDS
            END

     ---------------------------------------------------------------------

                                    Заключение

            Теперь Вы готовы писать и устанавливать свои собственные драй-
         веры  устройств.  Руководствуйтесь  нашими  замечаниями и "MS-DOS
         Programmers Reference Manual" при возникновении каких-либо техни-
         ческих вопросов.
                                      ЙННННННННННН»
                                      є   Вывод   ЗД¬
                                      є программы є ¦
                                      ИНСНННННННННј ¦
                                        АДДДєДДДДДДДЩ
                         ЙННННННННННННННННННОНННННННННННННННННН»
                         є                  є                  є
                  ЙННННННvНННННН»    ЙННННННvНННННН»    ЙННННННvНННННН»
                  є Виртуальный ЗД¬  є Виртуальный ЗД¬  є Виртуальный ЗД¬
                  є дисплей #0  є ¦  є дисплей #1  є ¦  є дисплей #2  є ¦
                  ИНСНННННННННННј ¦  ИНСНННННННННННј ¦  ИНСНННННННННННј ¦
                    АДДДДєДДДДДДДДЩ    АДДДДєДДДДДДДДЩ    АДДДДєДДДДДДДДЩ
                         ИННННННННННННННННН>є<НННННННННННННННННј
                                            є
                                      ЙНННННvННННН»
                                      є  Монитор  ЗД¬
                                      є  Дисплей  є ¦
                                      ИНСНННННННННј ¦
                                        АДДДДДДДДДДДЩ

                     Рисунок 6-8. Драйвер виртуальных экранов

            Полезными драйверами были  бы,  например,  драйвер  матричного
         принтера,  поддерживающий графические команды (такие как "нарисо-
         вать линию") и преобразующий их к требуемому  принтером  формату,
         или драйвер терминала, поддерживающий виртуальные экраны (см.рис.
         6-8). Такой драйвер терминала может иметь несколько буферов в па-
         мяти, хранящих копии экранной информации. Посылая команды драйве-
         ру по IOCTL-каналу,  можно указать драйверу какой виртуальный эк-
         ран  должен  быть  обновлен и какой виртуальный экран должен быть
         отображен на реальном экране.  Если  Вы  успешно  написали  такой
         драйвер, то Вы можете заменить им существующий драйвер консоли.
             Список идей,  которые можно было бы реализовать в  драйверах,
         практически бесконечен.  Вероятно,  у Вас уже появилось несколько
         своих,  которые Вы хотели бы реализовать. Если у Вас хватает спо-
         койствия  и  терпения то ничто не может помешать Вам в этом,  так
         что дерзайте!



                     Глава 7. ИСПОЛЬЗОВАНИЕ РАСШИРЕННОЙ ПАМЯТИ

                     Урок истории
                     Менеджер расширенной памяти
                     Интерфейс прикладной программы EMS
                     Написание программ, использующих расширенную память
                     Системное программное обеспечение
                     Краткое содержание
                     Библиография
                     Программы интерфейса низкого уровня и пример приложения

              Включение спецификации расширенной памяти в MS-DOS 4.0  сде-
         лало  полноправным  данный  стандарт в качестве метода увеличения
         памяти во всех системах  MS-DOS.  Несмотря  на  все  возрастающее
         быстродействие и сложность IBM-совместимых персональных компьюте-
         ров на базе DOS,  их предельные аппаратурная и программная произ-
         водительность была подвержена ограничениям,  накладываемым струк-
         турой системы  и  архитектурой  процессора  8088,  встроенными  в
         исходный персональный компьютер IBM. До недавнего времени один из
         таких пределов,  барьер 640К,  являлся постоянным источником зат-
         руднений для пользователей и программистов систем MS-DOS. Обозна-
         чающий  максимальную  величину  ОЗУ пользователя, поддерживаемого
         MS-DOS, предел  памяти  в  640К  появлялся в качестве постоянного
         препятствия к неумолимой тенденции разрабатывать и применять  бо-
         лее мощные программные средства.
              Пользователи, требующие сложных баз данных, электронных таб-
         лиц,  графических интерфейсов и резидентных утилит,  хотят приме-
         нять эти средства одновременно под управлением многозадачной сис-
         темы с окнами.  Разработчики готовы удовлетворить такие запросы с
         помощью резидентных отладчиков,  инструкций реального  времени  и
         наборов средств интерфейса пользователя. Обе группы предпринимали
         отчаянные усилия при ограничении в 640К без определенного решения
         для тупика DOS.
              Вся промышленность персональных компьютеров вступила в  нас-
         тоящее  время  в  сотрудничество для создания работающего решения
         для преодоления предела памяти в MS-DOS по схеме расширения памя-
         ти,  именуемой расширенной памятью, которая обеспечивает програм-
         мам MS-DOS доступ максимум к 32 мегабайтам ОЗУ помимо  640К, под-
         держиваемых  MS-DOS.  К  сожалению,  расширенная  память не может
         использоваться приложениями MS-DOS автоматически. Любая программа
         должна писаться специальным образом для ее распознавания и приме-
         нения.  Хотя процесс распознавания и применения не является авто-
         матическим,  он не особенно сложный или загадочный.  Данная глава
         обеспечит Вам основы и знания,  необходимые для добавления  этого
         мощного средства к Вашей собственной базе программирования.
              Требования производительности и гибкости,  которые пользова-
         тели предъявляют к программному обеспечению,  практически диктуют
         программам прямое управление аппаратурой персонального компьютера
         в  обход DOS и BIOS для получения максимума из аппаратуры семейс-
         тва персональных компьютеров.  Создание таких программ -  сложная
         задача  для разработчиков,  которые не только должны владеть тон-
         костями аппаратуры персонального компьютера и его  различных плат
         адаптеров ввода/вывода, но также обращать специальное внимание на
         приспособление к различиям в аппаратуре установленной  базы  сис-
         тем, совместимых с IBM PC.
              К счастью,  разработчики приложений, которые используют рас-

                                      - 7-2 -
         ширенную память,  могут не вникать в неясные подробности оборудо-
         вания плат расширения памяти для получения высокой производитель-
         ности   и   гибкости.   Полный   доступ   к   расширенной  памяти
         обеспечивается хорошо документированными аппаратурно независимыми
         программными интерфейсами, которые были разработаны и поставляют-
         ся основными производителями аппаратного и программного обеспече-
         ния персонального компьютера.
              Наиболее известным из программных интерфейсов является Lotus
         /Intel/Microsoft Expanded Memory  Specification  (LIM EMS) - спе-
         цификация расширенной памяти названных  фирм.  Впечатляющее  мно-
         жество  коммерчески  доступных  систем пользуется расширенной па-
         мятью LIM EMS для  снятия  ограничения  DOS.  Windows  2.0  фирмы
         Microsoft  и DESQView фирмы Quarterdeck Office Systems используют
         расширенную память для облегчения нескольких многозадачных прило-
         жений.  Lotus 1-2-3  и Symphony,  Excel фирмы Microsoft,  AutoCAD
         фирмы Autodesk и другие популярные прикладные программы - все ис-
         пользуют  расширенную  память  для  предоставления  пользователям
         средств решения более объемных и более  сложных  задач  реального
         мира. PC-DOS и MS-DOS 4.0 содержат драйверы, которые поддерживают
         стандарт LIM EMS,  как часть операционной  системы,  хотя  ранние
         версии  DOS  4.0,  по-видимому,  не поддерживают функцию 19h EMS,
         функцию Get/Set Handle Attribute,  а первые поставленные с систе-
         мой драйверы содержали ошибки. PC-DOS и MS-DOS 4.0 используют па-
         мять LIM EMS 4.0 для буферизации секторов из открытых файлов (уп-
         равляемая  параметром  BUFFERS  в  файле  CONFIG.SYS)  и проверки
         элементов каталогов (которую команда FASTOPEN предоставляет с DOS
         3.3 и более поздних версий). Первые версии MS-DOS 4.0, видимо, не
         используют средства многозадачности LIM EMS 4.0  никоим  образом.
         (Заметьте, что во всей данной главе, всегда когда мы ссылаемся на
         версию 4.0 MS-DOS, мы также включаем и PC-DOS 4.0, если специаль-
         но не указано иное.)
              В данной главе проводится глубокий обзор методов,  необходи-
         мых  для  использования  расширенной  памяти  в Ваших собственных
         программных проектах.  Расширенная память может быть мощным, мно-
         госторонним  средством,  увеличивающим возможности Ваших программ
         для обработки более объемных задач,  для быстрого доступа к боль-
         шим базам данных по заказу,  хранящихся на диске,  для разделения
         данных с другими программами или для уменьшения памяти DOS, кото-
         рая им требуется.
              Вы можете удивиться,  когда узнаете,  что  Ваш  персональный
         компьютер   даже не нуждается в каком-либо специальном оборудова-
         нии или платах дополнительной памяти для Вас,  чтобы  записывать,
         тестировать  и выполнять приложения расширенной памяти.  В данной
         главе объясняется, каким образом программное обеспечение эмуляции
         расширенной  памяти  может  снабдить  Вас недорогим средством для
         разработки приложений расширенной памяти.
              Вы узнаете, как расширенная память подгоняется к архитектуре
         аппаратурного и программного обеспечения  персональных  компьюте-
         ров,  совместимых  с  IBM  PC.  Вы также узнаете о соглашениях по
         программированию и протоколах, которые необходимы для того, чтобы
         использовать расширенную память без вмешательства в другие прило-
         жения, включая:
              - Как определить,  когда на персональном компьютере присутс-
                твует расширенная память, и, если присутствует, то, сколь-
                ко ее установлено.
              - Как разместить,  освободить и работать с до 32 мегабайтами
                расширенной  памяти,  с  помощью интерфейса прерывания 67h

                                      - 7-3 -
                менеджера расширенной памяти,  определенного спецификацией
                расширенной памяти Lotus/Intel/Microsoft.
              - Как  эксплуатировать  функциональные  и легко используемые
                усовершенствования,  включенные  в  последнюю версию (4.0)
                LIM EMS.
              - Как  интерпретировать  и  реагировать  на  условия ошибок,
                возвращаемые подсистемой расширенной памяти.

              В данной главе мы дадим ссылочные материалы,  подробно опре-
         деляющие конкретный механизм прерываний и соглашения по использо-
         ванию регистров, требующиеся для применения интерфейса программи-
         рования  LIM  EMS.  Мы также поможем Вам определять типы структур
         данных,  которые наиболее пригодны для сохранения  в  расширенной
         памяти.
              Мы проследим историю и мотивы спецификации расширенной памя-
         ти  Lotus/Intel/Microsoft и усовершенствованной спецификации рас-
         ширенной памяти AST/Qadram/Ashton-Tate (AQA  EEMS).  Вы  увидите,
         каким  образом  различные средства этих стандартов включаются для
         способствования преодоления ограничениям памяти DOS для почти лю-
         бого типа программ, включая драйверы устройств, утилиты, остающи-
         еся резидентно в памяти после  завершения,  и  усовершенствования
         ОС.  В данной главе будут приведены соображения по технике и сов-
         местимости,  относящиеся к каждой из спецификаций расширенной па-
         мяти,  включая поддержку EMS 4.0,  встроенную в MS-DOS 4.0,  так,
         что приложения,  которые Вы пишете, будут совместимы с широчайшим
         множеством реализаций расширенной памяти.
              Для того,  чтобы Вы могли начать программировать расширенную
         память, в данной главе содержится ряд программ интерфейса низкого
         уровня,  написанных на языке С фирмы Microsoft версия 5.0, и при-
         мер  приложения.  Пример состоит из двух законченных,  работающих
         программ и дает Вам демонстрацию от начала до конца ключевых  ме-
         тодов  программирования  LIM EMS,  представленных в данной главе.
         Некоторые из сложных способов, иллюстрируемых данным приложением,
         включают  в  себя разделение данных между двумя программами и ис-
         пользование расширенной памяти в программе обслуживания  прерыва-
         ния.
              Мы начинаем рассмотрение опций расширенной памяти  с истории
         и  событий в промышленности персональных компьютеров, совместимых
         с IBM, которые вызвали ее разработку и использование.

                                   Урок истории

              В 1981 г.  типичный персональный компьютер мог адресовать не
         более 64 килобайт основной памяти.  Серьезные программисты, рабо-
         тавшие на этих машинах,  тратили неординарное количество  времени
         для  наскребывания  последних нескольких байтов,  необходимых для
         введения еще одного дополнительного  средства.  Появление  персо-
         нального  компьютера IBM с ОЗУ,  которое было на порядок величины
         больше, чем у предшественников, казалось предлагает продолжитель-
         ный перерыв в нехватке памяти.
              Едва прошло три года,  аналог закона Паркинсона  для  памяти
         ЦВМ (работа расширяется,  чтобы заполнить время, доступное для ее
         выполнения) обеспечил для IBM PC то же,  что было для всех преды-
         дущих поколений ЦВМ. Сегодня электронные таблицы, интегрированные
         приложения,  сети и поток резидентных утилит сделали пространство
         памяти IBM PC столь же заполненным и ограниченным,  как и у пред-
         шественников.

                                      - 7-4 -
              Идеального решения для предела ОЗУ не существует. Даже, хотя
         микропроцессор Intel 8088 в IBM PC поддерживает  адресное  прост-
         ранство  1  Мбайт,  384 Кбайт адресного пространства между 640К и
         пределом адресации 1 Мбайт зарезервированы для буферов видеоадап-
         теров,  системного  и  Бейсик  ПЗУ и модулей ПЗУ BIOS других плат
         ввода/вывода.  Новые приложения PC-DOS загнаны в ловушку в грани-
         цах  640 Кбайт пользовательского ОЗУ,  с которым семейство IBM PC
         было рождено.
              Одним из  традиционных  решений  проблем  с памятью является
         оверлей.  Оно часто используется приложениями персональных компь-
         ютеров,  в которых компоненты кодов программ можно разложить над-
         лежащим образом. Для других типов приложений, таких как электрон-
         ные таблицы,  данный подход эффективен не до конца.  Требования к
         памяти для данного типа приложений вызываются главным образом по-
         тенциально  неограниченным  размером их центральных структур дан-
         ных, а не размерами их выполняемых кодов.

                                      LIM EMS

              В отсутствие   универсального   решения   корпорации   Lotus
         Development,  Intel и Microsoft объединились для получения схемы,
         позволяющей индивидуальным приложениям работать за пределом 640К,
         который накладывается реализацией DOS IBM PC. Результат называет-
         ся спецификацией расширенной памяти Lotus/Intel/Microsoft или LIM
         EMS.Intel производит платы,  содержащие эту память, Lotus адапти-
         ровала свою электронную таблицу для использования этой  памяти, а
         Microsoft убедилась,  что спецификации будут отвечать требованиям
         усовершенствований ОС, над которыми ведется работа.
              LIM EMS  фактически  определяет новую реализацию популярного
         старого действия при недостатке адресного пространства:  переклю-
         чение  банков памяти.  Вкратце,  схемы переключения банков памяти
         работают с использованием  электронных  переключателей  (в  форме
         программно  адресуемых портов ввода/вывода),  которые динамически
         изменяют отображение физических блоков памяти на часть процессор-
         ного  адресного пространства.  В этом случае компьютерная система
         может обращаться к большему числу байтов физической  памяти,  чем
         обеспечивает архитектура памяти процессора,  хотя в данный момент
         времени не все байты физической памяти программе доступны.
              Как техническое  решение  проблемы пространства памяти,  LIM
         EMS вовсе не более впечатляющая или эффективная, чем схемы перек-
         лючения банков, присутствовавшие во многих ЦВМ, созданных в тече-
         ние эры микропроцессоров 6502 и 8080. Что действительно имеет LIM
         EMS, так это - поддержку нескольких лидеров рынка в промышленнос-
         ти персональных компьютеров и документацию, которая легко доступ-
         на для разработчиков программного обеспечения и не требует запро-
         са.  Это обстоятельство - весьма  большая  редкость  в  настоящее
         время, когда основные поставщики программного обеспечения, по-ви-
         димому,  сконцентрировали усилия на продаже средств создания при-
         ложений разработчикам по ценам от 500 до 3000 долларов.
              До выхода версии 4.0 PC-DOS IBM  оставалась  нейтральной  по
         отношению к LIM EMS, выбрав поддержку принятия OS/2 разработчика-
         ми приложений в качестве более устойчивого решения проблемы памя-
         ти.  В то время как OS/2 представляет решение на долгий срок, для
         большинства пользователей MS-DOS относительное запоздание ее вве-
         дения и отсутствие полной совместимости вверх с большой долей ги-
         гантского существующего оборудования и  программного  обеспечения
         ограничивают ее немедленную пригодность в качестве решения.
              Поскольку LIM EMS практична, немедленно доступна и совмести-
         ма вверх со всеми существующими системами, работающими под управ-
         лением MS-DOS, она стала коммерчески и технически успешным спосо-
         бом  для  программ DOS для преодоления барьера 640К.  Фактически,
         некоторые промышленные наблюдатели   предполагают,  что эффектив-
         ность решения LIM EMS в действительности удлинит  жизнь систем на
         основе MS-DOS на несколько лет за точку их предполагавшегося тех-
         нологического устаревания.

              LIM EMS  3.2

              Первая широко поддержанная версия LIM EMS за номером 3.2 бы-
         ла опубликована в сентябре 1985 г.   Она определила протокол рас-
         ширенной памяти, обеспечивавший надлежащим образом спроектирован-
         ные  программные  приложения  памятью  для  данных   или   кодов,
         переключаемую  банками,  объемом до 8 Мбайт.  Данная спецификация
         включала в себя средства,  которые позволяли нескольким  активным
         приложениям использовать  эту  память  одновременно без взаимного
         влияния.
              Способность для  многих  программ,  использующих расширенную
         память, сосуществовать особенно выгодна разработчикам резидентных
         программ,  по завершении остающихся в памяти,  по крайней мере, в
         двух аспектах.  Во-первых,  такая программа может  хранить  часть
         своих  данных или кода в расширенной памяти,  что уменьшает объем
         обычно  занимаемой ею памяти.  Во-вторых, конфликтов между такими
         и другими программами из-за использования расширенной памяти мож-
         но избежать,  так как LIM EMS определяет конкретные соглашения по
         программированию,  которые предотвращают появление таких конфлик-
         тов.

                          Идеи и терминология LIM EMS 3.2

              Основная схема LIM EMS 3.2 работает следующим образом:
              1. На  машине может быть установлено до 8 Мбайт ОЗУ на одной
         или нескольких платах. В отличие от многообразных плат памяти на-
         копитель  на этих платах делится на страницы по 16 Кбайт.  Расши-
         ренная память не адресуется приложениями DOS непосредственно, так
         как она не появляется в младших 640 К адресного пространства пер-
         сонального компьютера.
              2. Эти платы памяти также содержат набор регистров соответс-
         твия, которые управляются программно для установления отображения
         какой-либо  из  16-Кбайтных страниц на плате (платах) расширенной
         памяти на любую из четырех 16-Кбайтных зон  в  64-Кбайтной  части
         адресного пространства персонального компьютера, именуемого стра-
         ничным кадром. Страничный кадр размещается где-то в резервном ад-
         ресном пространстве персонального компьютера над 640К и ниже пре-
         дела адресации 8086/8 - 1  М.  Каждая  зона  в  страничном  кадре
         называется  физической  страницей  и  определяется  числом 0 - 3.
         Страничный кадр образует окно, через которое правильно написанная
         программа  может  получить  доступ  ко  всей емкости памяти платы
         (плат) расширенной памяти.  Процесс изменения содержимого регист-
         ров  отображения плат для обеспечения доступности страницы расши-
         ренной памяти программе называется страничным отображением.
              3. Управление системой расширенной памяти,  включая странич-
         ное отображение,  выполняется программной компонентой, называемой
         Менеджер  расширенной  памяти  или ЕММ (Expanded Memory Manager),
         которая поставляется изготовителем платы расширенной  памяти.  Во

                                      - 7-6 -
         многом так же,  как DOS и BIOS обеспечивают программный интерфейс
         между приложением и аппаратурой ЦВМ,  находящейся ниже него,  ме-
         неджер расширенной памяти обеспечивает программный интерфейс меж-
         ду приложением и системой расширенной памяти.  Менеджер расширен-
         ной  памяти  загружается в память так же,  как драйвер клавиатуры
         DOS  во время загрузки и сообщается с программами через программ-
         ное прерывание 67h, используя механизм передачи параметров, срав-
         нимый с интерфейсом прерывания 21h DOS.
              4. По запросу программы менеджер расширенной памяти размеща-
         ет набор из одной или более логических страниц для  программы. Он
         также размещает обработчик (handle), который программа использует
         в последующих запросах расширенной памяти к менеджеру расширенной
         памяти,  для определения набора страниц расширенной памяти, с ко-
         торыми нужно работать.  Во многом таким же образом как обработчик
         файлов DOS используется ОС для отслеживания файлов, открытых каж-
         дой программой,  обработчики расширенной памяти используются  ме-
         неджером  расширенной  памяти для отслеживания множества активных
         страниц расширенной памяти каждой программы.  Формат  обработчика
         неопределен, за исключением того факта, что он является 16-битной
         величиной.  Номера логических страниц,  связанные с обработчиком,
         отсчитываются относительно нуля до значения, на единицу меньшего,
         чем количество страниц, запрошенных программой.
              5. Когда  от менеджера расширенной памяти запрашивается обс-
         луживание, программа определяет конкретную страницу в 16К  расши-
         ренной  памяти,  которую  она желает использовать,  путем задания
         комбинации обработчика и номера логической страницы.
              Примечание: Ранние  версии  спецификации  используют  термин
         идентификатор (ID) процесса, а не термин обработчик для ссылки на
         множество страниц расширенной памяти. Разработчики LIM EMS внача-
         ле предполагали, что каждая программа будет размещать только одно
         множество страниц,  подразумевая соответствие один к одному между
         программами и идентификаторами процессов.  На  практике,  однако,
         многие  программы  запрашивают более одного набора страниц расши-
         ренной памяти. Как Вы увидите позже, это законный и полезный при-
         ем программирования.  Однако,  факт,  что одна программа могла бы
         обладать более  чем одним идентификатором процесса EMS, запутывал
         многих пользователей, что и подсказало изменение терминологии.
              6. Менеджер расширенной памяти LIM EMS 3.2 системы расширен-
         ной  памяти  обеспечивает  14  функций,  вызываемых пользователем
         (плюс две резервные функции),  которые приложения используют  для
         получения  информации  о  страницах расширенной памяти и работы с
         ними.

                    Улучшенная спецификация расширенной памяти

              Вскоре сформировалось второе объединение в компьютерной про-
         мышленности  для  продвижения улучшенной спецификации расширенной
         памяти. Результатом его усилий, которое поддерживалось корпораци-
         ями AST Research, Quadram и Ashton-Tate была вверх совместимая (с
         LIM EMS 3.2) схема расширенной памяти, именуемая AQA EEMS.

                             Ограничение размера окна
              Основное ограничение  схемы  LIM  EMS 3.2 с точки зрения AQA
         заключалось в ничтожном размере окна страничного кадра, равном 64
         Кбайт.  Через такое небольшое окно в расширенную память программа
         может иметь доступ только к 4 16-Кбайтным  страницам  расширенной
         памяти из 512 возможных (8 мегабайт), поддерживаемых спецификаци-

                                      - 7-7 -
         ей.  AQA EEMS позволяет программе обращаться,  по крайней мере  в
         теории,  к  максимум  64 16-Кбайтных страниц расширенной памяти в
         любой данный момент времени. Быстрый подсчет (думающие в двоичном
         коде могут сдвигать биты) показывает,  что при этом память,  при-
         годная для отображения должна составлять до 1 Мбайта!  Но  подож-
         дите,  спросите Вы, как там насчет всех видеобуферов и ПЗУ BIOS в
         пространстве над 640К, не говоря о DOS и приложениях в пространс-
         тве ниже 640К? Учтено ли это в регистрах отображения?
              Нет. Действительность,  с которой имеет дело EEMS та  же,  с
         которой  обращается LIM EMS;  AQA EEMS просто работает с этой ре-
         альностью более смело.  LIM EMS 3.2 - пример консервативной в са-
         мом худшем смысле философии проектирования. Например, разработчи-
         ки определили,  что персональный компьютер с  платой  улучшенного
         графического адаптера (EGA) и сетевой платой, содержащей ПЗУ, мо-
         жет обладать только 64 К неиспользуемого  адресного  пространства
         над  640К.  Адресным пространством ниже 640К уже твердо управляет
         DOS.  Таким  образом,  для  разработчиков  LIM  EMS  3.2  остался
         64-Кбайтный страничный кадр.
              Разработчики AQA оценивали задачу с другой точки зрения. Они
         рассуждали,  что стандартный персональный компьютер с CGA или MDA
         обладает достаточным адресным пространством выше 640К для отобра-
         жения,  по  крайней  мере,  12 страниц EMS одновременно.  И фирма
         Quarterdeck Office Systems,  не уступая ничего  DOS,  разработала
         расширитель ОС, называемый DESQView, который может менять местами
         программы ниже и выше 640К.  Если бы DESKView была способна  под-
         ставлять почти мгновенную операцию отображения страниц вместо от-
         носительно медленного процесса обмена местами (своппинга), ее по-
         тенциал  в  качестве высокопроизводительной многозадачной системы
         для IBM PC сильно возрос бы.
              Фирма AST разработала плату,  способную к отображению памяти
         через  адресное  пространство  1  Мбайт,  снабдив  таким  образом
         DESKView механизмом,  необходимым для быстрого переключения между
         программами. Практические ограничения, однако, препятствуют пере-
         отображение тех частей адресного пространства, которые занимаются
         ПЗУ BIOS, дисплейным адаптером и памятью на основной плате, необ-
         ходимой для загрузки системы.
              Схема EEMS на рынке пользуется ограниченным успехом. Сочета-
         ние путаницы в продаже продукции,  вызванной AST и Quadram, и вя-
         лой поддержки ее функциональных расширений другими основными раз-
         работчиками  программного  обеспечения  персонального  компьютера
         ограничили ее рост в качестве главного  альтернативного стандарта
         для LIM EMS. Разработчики были довольны, что EEMS, по крайней ме-
         ре,  совместима вверх с LIM EMS,  но многие из них,  очевидно, не
         думают, что данные расширения, которые она предлагает, стоят  за-
         трат, если расширения влекут за собой потерю  совместимости  вниз
         со схемой LIM.

                                    LIM EMS 4.0

              Вне зависимости  от  результатов  рынка  кто-то в лагере LIM
         должен был найти в AQA EEMS что-либо  привлекательное.  Поскольку
         приближался  к  выпуску  продукт фирмы Microsoft - Windows 2.0 (а
         величина памяти, требовавшаяся для работы с ним, все росла), идея
         получить возможность переключать задачи быстро в памяти ниже 640К
         должна была приобрести заметную привлекательность. И  может быть,
         пространство  над 640К не было заполнено целиком,  поскольку даже
         IBM PS/2 с контроллером дисплея VGA  имел  пространство  для,  по

                                      - 7-8 -
         крайней мере, шести 16-Кбайтных страниц.
              В августе 1987 г. группа Lotus/Intel/Microsoft объявила спе-
         цификацию LIM EMS 4.0.  Новая спецификация включила в себя в сущ-
         ности все улучшенные свойства AQA EEMS и добавила  еще  несколько
         собственных,  которые будут рассмотрены подробно позднее в данной
         главе.  Группа AQA получила столь сильное впечатление (или сомне-
         валась  в мудрости предпринятия еще одной схватки на рынке),  что
         вскоре объявила о своей публичной поддержке спецификации 4.0.
              В 1988 г. IBM и Microsoft включили драйверы EMS в версию 4.0
         операционных систем PC-DOS и MS-DOS. Драйверы EMS в ранних выпус-
         ках  содержали  ошибки  и технические специалисты явно определили
         отсутствие поддержки для функции 19h EMS,  функции "Получения/Ус-
         тановки   атрибута  обработчика",  хотя во всех других отношениях
         реализация EMS DOS 4.0 соответствует спецификации  LIM  EMS  4.0.
         Данная  глава обращается к спецификации LIM EMS 4.0 независимо от
         того,  есть ли это в операционной системе DOS 4.0,  поскольку  во
         всех отношениях сопряжение с системами EMS 4.0 идентично.
              Как и в 1988 г.  все основные участники в области MS-DOS ос-
         тановились на спецификации LIM EMS 4.0. Унификация стандарта рас-
         ширенной памяти снимает, по крайней мере, одну заботу с умов раз-
         работчиков программного обеспечения, которым больше не приходится
         беспокоиться, что же лучше - быть в безопасности с EMS или пофан-
         тазировать с расширениями EEMS.
              На рис.  7-1 показано, каким образом до 32 Мбайт расширенной
         памяти  адресуются в пределах двух различных областей 1-мегабайт-
         ного адресного пространства персонального компьютера IBM. В зави-
         симости  от использования ПЗУ BIOS и адаптером дисплея пространс-
         тва между 640К и 1024К,  от четырех  до  12  страниц  по  16Кбайт
         расширенной памяти могут отображаться на эту область. Пригодная к
         отображению обычная память (черта AQA EEMS и LIM EMS  4.0)  может
         применяться только расширениями операционной системы.
       ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
       ¦ Некоторые свойства новой спецификации не были четко задокументи-¦
       ¦рованы в издании 1987 г.   Пара пересмотренных граничных  условий¦
       ¦для  некоторых из функций версии 3.2 вводила в созданную специфи-¦
       ¦кацию 4.0 несовместимость  вниз  между  версиями.  Пересмотренная¦
       ¦спецификация 4.0, прояснившая новые свойства и устранившая несов-¦
       ¦местимость была опубликована в октябре 1987 г.                   ¦
       АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
              Количество функций,  поддерживаемых спецификацией 4.0, вдвое
         - до 28 - повысило число функций,  вызываемых пользователем,  до-
         ступных в спецификации 3.2. Большинство из новых функций обладают
         несколькими подфункциями. Таким образом, весь предмет расширенной
         памяти теперь в несколько раз больше,  чем был. Официальный доку-
         мент  спецификации  для  LIM EMS 4.0,  внешность которого следует
         стилю Технического справочного руководства DOS, более чем удвоил-
         ся по объему по отношению к своему предшественнику версии 3.2.
              В то время как в данной главе будет представлено  так  много
         подробностей, насколько это возможно, для иллюстрации идей расши-
         ренной памяти,  серьезные разработчики могут обнаружить,  что она
         не  может служить в качестве полной замены официальной специфика-
         ции.  Вы можете получить копию  спецификации  непосредственно  от
         Intel,  позвонив (800) 538-3373 в США и Канаде или (503) 629-7354
         везде в других местах.
              Один из возможных подходов к изучению EMS мог бы заключаться
         в изучении функций, совместимых с LIM EMS 3.2, и затем в рассмот-
         рении функций,  добавленных спецификацией 4.0. Как Вы вскоре уви-

                                      - 7-9 -
         дите,  функции,  добавленные EMS 4.0,  являются более,  чем  верх
         совместимыми расширениями:  во многих случаях новые функции пред-
         лагают намного более простые способы выполнения  задач управления
         расширенной  памятью,  чем  те,  которые были возможны со старыми
         функциями. Таким образом, в данной главе функции версий 3.2 и 4.0
         представляются совместно.

                 LIM EMS 4.0 по сравнению с LIM EMS 3.2 и AQA EEMS

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

                           Соображения по совместимости

              Если Вы намереваетесь писать приложение  с  расширенной  па-
         мятью, которое может использовать самую большую установленную ба-
         зу систем расширенной памяти,  то Вы, вероятно, не ошибетесь, ис-
         пользуя  только те функции менеджера расширенной памяти,  которые
         поддерживаются спецификацией LIM EMS 3.2.  Однако, Вы должны учи-
         тывать,  что  большинство  основных  плат расширения и поставщики
         программного обеспечения весьма одобрили спецификацию LIM EMS 4.0
         вскоре  после  ее  появления.  Такая степень принятия LIM EMS 4.0
         практически гарантирует ее положение в качестве главного стандар-
         та расширенной памяти. Следующие факторы поддерживают это утверж-
         дение:
              - Пользователю не приходится покупать никакое новое оборудо-
         вание для использования приложений, которые написаны для специфи-
         кации LIM EMS 4.0. Более старые платы расширенной памяти, спроек-
         тированные для спецификации LIM  EMS  3.2,  могут  поддерживаться
         спецификацией 4.0 - изготовителю приходится только написать новый
         менеджер расширенной памяти,  для реализации вызовов функций 4.0.
              - Корпорация  Intel снабжает владельцев своих вышеупомянутых
         плат расширенной памяти менеджером расширенной памяти,  поддержи-
         вающим спецификацию 4.0 бесплатно.
              - Практически любое новое оборудование расширенной памяти (и
         эмулятор расширенной памяти) поддерживают LIM EMS 4.0.
              - Программные продукты высокой видимости,  такие как Windows
         2.0 фирмы Microsoft (менеджер представления),  Excel (электронная
         таблица)  и DESKView 2.0 фирмы Quarterdeck Office Systems (много-
         задачная среда) - все используют средства LIM EMS  4.0.  Грядущие
         усовершенствования  электронных таблиц и баз данных других основ-
         ных  поставщиков,   ожидается,  также  будут  обладать поддержкой
         LIM EMS 4.0.
              Так как соразработчики спецификации AQA EEMS  объявили,  что
         их  новые  продукты  расширенной  памяти будут согласовываться со
         спецификацией LIM EMS 4.0, кажется, не следует давать совет прог-

                                      - 7-10 -
         раммистам  применять  функции  ЕЕМS  в новых программах.  Поэтому
          ЪДДДДДДДДДДДДД¬10000000H(16Mb) Ъ Д Д Д Д ДЪДДДДДДДД¬
          ¦     1       ¦                ¦        / ¦        ¦
          ¦             ¦                ¦      /   ¦        ¦
          ¦/\/\/\/\/\/\/                 ¦    /     ¦        ¦
           /\/\/\/\/\/\/\                ¦  /       ¦        ¦
          ¦             ¦                ¦          ¦        ¦
          АДДДДДДДДДДДДДЩ              / ¦          ¦        ¦
          ЪДДДДДДДДДДДДД¬100000H(1Mb)/   ¦          ¦        ¦
          ¦      2      ¦          /     ¦          ¦        ¦
          ГДДДДДДДДДДДДДґF000H(960Kb)    ¦          ¦        ¦
          ¦     /\  2a  ¦      /         ¦          ¦        ¦
          ГДДДДДґГДДДДДДґE0000H(896Kb)   ¦          ¦        ¦
          ¦     ¦АДДДДДДБДДДДДЩ          ¦          ¦        ¦
          ¦  3  ¦ЪДДДДДДВДДДДД¬   6      ¦          ¦   7    ¦
          ¦     \/      ¦     ¦          ¦          ¦        ¦
          ГДДДДДДДДДДДДДґC0000H\         ¦          ¦        ¦
          ¦      4      ¦(768Kb) \       ¦          ¦        ¦
          ГДДДДДДДДДДДДДґB0000H    \     ¦          ¦        ¦
          ¦      4a     ¦(704Kb)     \   ¦          ¦        ¦
          АДДДДДДДДДДДДДЩ              \ ¦          ¦        ¦
                                         ¦          ¦        ¦
          ЪДДДДДВВДДДДДД¬A0000H(640Kb)   ¦ \        ¦        ¦
          ¦     ¦АДДДДДДДДДДДДДДДДДДДДДДДЩ   \      ¦        ¦
          ¦     ¦ЪДДДДДДДДДДДДДДДДДДДДДДД¬  8  \    ¦        ¦
          Г Д Д \/Д Д Д ґ40000H(256Kb)   ¦       \  ¦        ¦
          ¦     5       ¦                ¦         \¦        ¦
           \/\/\/\/\/\/\¦                А Д Д Д Д ДАДДДДДДДДЩ
          ¦\/\/\/\/\/\/\
          ¦             ¦
          АДДДДДДДДДДДДДЩ0000H(0Kb)

            Рис.7-1. Расширенная память и адресное пространство IBM PC

         1 - расширенная память (AT, PS/2 с процессорами 80286/80386),
         2 - PC/XT/AT PS/2 ROM-BIOS,
         2a- PC/AT PS/2 ROM-BIOS,
         3 - ROM дополнительных адаптеров ввода-вывода,
         4 - EGA/VGA/MDA/CGA дисплейный буфер,
         4a- EGA/VGA дисплейный буфер,
         5 - память пользователя,
         6 - страничный кадр LIM EMS, от 4 до 12 16-Кбайтных физических
             страниц,
         7 - до 32 Мбайт расширенной памяти,  от 0 до 2048 16-Кбайтных
             логических страниц,
         8 - пригодная  к отображению обычная память (для использования
             только операционной системой), 0-24 16-Кбайтных физических
             страниц

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



                                      - 7-11 -
                              Технические соображения
              С точки  зрения разработчика приложения решение о применении
         функций LIM EMS 4.0 в программе, не ограничиваясь только функция-
         ми  LIM EMS 3.2,  вначале может выглядеть как вопрос предпочтения
         программиста.
              Это справедливо  на  одном уровне,  так как возможно для Вас
         выполнить любую разумную прикладную функцию,  требующую расширен-
         ной памяти, с помощью функций, которые доступны только в специфи-
         кации LIM EMS 3.2. Однако, используя функции более высокого уров-
         ня,  введенные  спецификацией  4.0,  Вы будете способны сократить
         объем кода,  который Вам придется написать для выполнения  многих
         обычных задач при работе с расширенной памятью. В частности, пос-
         ледние разделы данной главы покажут Вам,  как функции LIM EMS 4.0
         позволяют  передавать  большие  блоки  памяти между расширенной и
         обычной памятью и выполнять код в расширенной памяти одним  вызо-
         вом функции менеджера расширенной памяти.

                            Менеджер расширенной памяти

              Многие программисты  и пользователи персональных компьютеров
         связывают расширенную память только с набором БИС  памяти,  уста-
         новленных  на  плате памяти специального типа.  Это слишком узкое
         представление неудачно,  поскольку никакая часть LIM EMS не опре-
         деляет  чего-либо  об  оборудовании,  используемом для реализации
         системы расширенной памяти. Как было кратко упомянуто во введении
         к главе,  возможно обладать расширенной памятью на компьютере во-
         обще без всякого специального оборудования. Мы приведем обоснова-
         ние  данного  смелого заявления в последнем разделе данной главы,
         описывающем несколько реализаций расширенной памяти.
              Вне зависимости  от  конструкции  системы расширенной памяти
         каждый может включать программную компоненту,  именуемую менеджер
         расширенной  памяти,  который поддерживает программный интерфейс,
         определенный спецификацией расширенной памяти,  между  прикладной
         программой и лежащей ниже системой расширенной памяти.
              Программа менеджера расширенной  памяти  сама  упаковывается
         внутри  драйвера  символьного  устройства  DOS,  определенного  в
         CONFIG.SYS,  который загружается и активизируется  DOS  во  время
         загрузки. Она отличается от других драйверов устройств DOS в том,
         что сообщение между приложением и драйвером  устройства менеджера
         расширенной  памяти не проходит через файловую систему DOS в виде
         открытий, закрытий, чтений или записей. Скорее она использует ме-
         ханизм прерываний, весьма сходный с используемым DOS, в котором в
         регистрах передаются коды функций,  параметры и коды возврата на-
         зад и вперед. Основная причина того, что менеджер расширенной па-
         мяти упакован как драйвер устройства,  заключается в  том,  чтобы
         позволить  ему  загружаться  достаточно рано в процессе загрузки,
         так чтобы драйверы устройств (например,  диски на ОЗУ и устройств
         подкачки при печати) могли использовать расширенную память.

                       Функции менеджера расширенной памяти

              Для удовлетворения  спецификации LIM EMS 4.0 менеджер расши-
         ренной памяти должен реализовывать 28 разных вызываемых пользова-
         телем функций, у многих из которых есть много подфункций. Большое
         количество  функций  и  подфункций,  определенных  спецификацией,
         представляет  значительное  препятствие к разумному использованию
         расширенной памяти.

                                      - 7-12 -
              Сложность LIM  EMS 4.0 почти требует от разработчика понима-
         ния высшего уровня функций менеджера расширенной  памяти.  Прежде
         чем  мы  начнем копаться в механике использования этих функций из
         программ на ассемблере и языках высокого уровня, мы начнем подхо-
         дить  к  данной  задаче с разбиения функций менеджера расширенной
         памяти на пять категорий:
             Информационные      Возвращают  состояние  менеджера  расши-
                                 ренной памяти, а также количества ресур-
                                 сов расширенной памяти,  которые доступ-
                                 ны и которые используются приложениями
                                 спецификации расширенной памяти.
             Управление данными  Управляют размещением, освобождением, пе-
                                 редвижением,  отображением и разделением
                                 данных и кодов в расширенной памяти.
             Управление          Управляют сохранением и восстановлением
                                 контекстов состояния отображения менедже-
                                 ра  расширенной  памяти  драйверами  уст-
                                 ройств и  программами, резидентными в па-
                                 мяти после завершения.
             Ориентированные     Управляют переключением состояния отобра-
             на операционную     жения менеджера расширенной памяти между
             систему             несколькими,одновременно работающими при-
                                 ложениями при многозадачных средах,  та-
                                 ких   как    Microsoft    Windows    или
                                 Quarterdeck DeskView.
             Зарезервированные   Услуги, зависящие от аппаратуры, которые
                                 были удалены из документированной специ-
                                 фикации,когда была объявлена LIM EMS 3.2.

              В табл.7-1 представлены функции LIM EMS 3.2 и 4.0, принадле-
         жащие к каждому из предшествующих классов (также  см.  табл.7.2).
         Следует отметить, что некоторые функции относятся ко многим клас-
         сам и перечислены более, чем в одной категории.

                                                         Таблица 7-1
                             Функции LIM EMS 3.2 и 4.0
       ДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДД
                     ¦   Функции EMS 3.2       ¦  Дополнения EMS 4.0
       ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДД
       Информационные¦ (1)Получить состояние   ¦(21)Получить каталог об-
                     ¦                         ¦    работчика
                     ¦ (2)Получить адрес стра- ¦(25)Получить массив отоб-
                     ¦    ничного буфера       ¦    ражаемых физических
                     ¦ (3)Получить отсчет не-  ¦    адресов
                     ¦    размещенной страницы ¦
                     ¦ (7)Получить версию      ¦
                     ¦(12)Получить отсчет обра-¦
                     ¦    ботчика              ¦
                     ¦(13)Получить страницы об-¦
                     ¦    работчика            ¦
                     ¦(14)Получить все страницы¦
                     ¦    обработчика          ¦
                     ¦                         ¦
       Управление    ¦(4)Разместить страницы   ¦(17)Отображать/Не отобра-
         данными     ¦(5)Отображать/Не отобра- ¦    жать много страниц об-
                     ¦   жать страницу обработ-¦    работчика
                     ¦   чика                  ¦(18)Переразместить страницы

                                      - 7-13 -
       ДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДД
                     ¦(6)Освободить страницы   ¦(19)Получить/Установить
                     ¦                         ¦    аттрибуты обработчика
                     ¦                         ¦(20)Получить/Установить
                     ¦                         ¦    имя обработчика
                     ¦                         ¦(22)Изменить отображение
                     ¦                         ¦    страницы и перейти
                     ¦                         ¦(23)Изменить отображение
                     ¦                         ¦    страницы и вызвать
                     ¦                         ¦(24)Передвинуть/Обменять
                     ¦                         ¦    область памяти
                     ¦                         ¦
       Управление    ¦(8)Сохранить отображе-   ¦(16)Получить/Установить
       контекстом    ¦   ние страницы          ¦    частичное отображение
                     ¦(9)Восстановить отобра-  ¦    страницы
                     ¦   жение страницы        ¦(23)Изменить отображение
                     ¦(15)Получить/Установить  ¦    страницы и вызвать
                     ¦   отображение страницы  ¦(24)Передвинуть/Обменять
                     ¦                         ¦    область памяти
                     ¦                         ¦
       Ориентиро-    ¦   Нет                   ¦(26)Получить данные об
       ванные на     ¦                         ¦    оборудовании расширен-
       операционную  ¦                         ¦    ной памяти
       систему       ¦                         ¦(27)Разместить стандарт-
                     ¦                         ¦    ные/необработанные
                     ¦                         ¦    страницы
                     ¦                         ¦(28)Сменить набор регист-
                     ¦                         ¦    ров отображения
                     ¦                         ¦(29)Подготовить аппаратуру
                     ¦                         ¦    расширенной памяти для
                     ¦                         ¦    загрузки из памяти
                     ¦                         ¦(30)Разрешить/Запретить
                     ¦                         ¦    функции набора Опера-
                     ¦                         ¦    ционной системы/Среды
       Зарезерви-    ¦(10)Зарезервирована      ¦
       рованные      ¦(11)Зарезервирована      ¦
       ДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДД

                                                         Таблица 7-2
                       Функции менеджера расширенной памяти
       ДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДД
        N ¦  Наименование  ¦   Входные регистры    ¦ Выходные регистры
       ДДД†ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДД
        1 ¦Получить состо- ¦ AH: 40h (код функции) ¦ Нет
          ¦яние            ¦                       ¦
        2 ¦Получить адрес  ¦ AH: 41h (код функции) ¦ BX - адрес сегмента
          ¦сегмента стра-  ¦                       ¦ страничного кадра
          ¦ничного кадра   ¦                       ¦
        3 ¦Получить счетчик¦ AH: 42h (код функции) ¦ BX - кол-во нераз-
          ¦неразмещенных   ¦                       ¦ мещенных страниц
          ¦страниц         ¦                       ¦
        4 ¦Разместить      ¦ AH: 43h (код функции) ¦ DX - обработчик
          ¦страницы        ¦ BX: номера страниц для¦
          ¦                ¦ размещения            ¦
        5 ¦Отобразить/     ¦ AH: 44h (код функции) ¦ Нет
          ¦снять отобра-   ¦ AL: номер физической  ¦
          ¦жение страниц   ¦     страницы          ¦

                                      - 7-14 -
          ¦обработчика     ¦ BX: номер логической  ¦
          ¦                ¦     страницы          ¦
          ¦                ¦ DX: обработчик        ¦
        6 ¦Освободить      ¦ AH: 45h (код функции) ¦ Нет
          ¦страницы        ¦ DX: обработчик        ¦
        7 ¦Получить версию ¦ AH: 46h (код функции) ¦ AL: двоично-дес.код
          ¦                ¦                       ¦ версии менеджера рас-
          ¦                ¦                       ¦ ширенной памяти
        8 ¦Сохранить отоб- ¦ AH: 47h (код функции) ¦ Нет
          ¦ражение страниц ¦ DX: обработчик        ¦
        9 ¦Восстановить    ¦ AH: 48h (код функции) ¦ Нет
          ¦отображение     ¦                       ¦
          ¦страниц         ¦                       ¦
       10 ¦Зарезервировано ¦                       ¦
       11 ¦Зарезервировано ¦                       ¦
       12 ¦Получить счетчик¦ AH: 4Вh (код функции) ¦ BX - кол-во обработ-
          ¦обработчика     ¦                       ¦ чиков в использовании
       13 ¦Получить страни-¦ AH: 4Сh (код функции) ¦ BX - кол-во страниц,
          ¦цы обработчика  ¦ DX: обработчик        ¦ размещенных для ука-
          ¦                ¦                       ¦ занного обработчика
       14 ¦Получить все    ¦ AH: 4Dh (код функции) ¦ BX - кол-во использу-
          ¦страницы обра-  ¦ ES:DI: массив страниц ¦ емых обработчиков
          ¦ботчика         ¦        обработчика    ¦
       15 ¦Получить отобра-¦ AX: 4Е00h (код функции) Массив отображения
          ¦жение страниц   ¦ ES:DI: массив отображе- страниц назначения<--
          ¦                ¦ ния страниц назначения¦ состояние отображения
          ¦                ¦                       ¦ менеджера расширенной
          ¦                ¦                       ¦ памяти
          ¦Установить отоб-¦ AX: 4Е01h (код функции) Состояние отображения
          ¦ражение страниц ¦ ES:DI: массив отображе- менеджера расширенной
          ¦                ¦ ния страниц-источников¦ памяти<--массив отоб-
          ¦                ¦                       ¦ ражения страниц-ис-
          ¦                ¦                       ¦ точников
          ¦Получить и уста-¦ AX: 4Е02h (код функции) Массив отображения
          ¦новить отображе-¦ ES:DI: массив отображе- страниц назначения<--
          ¦ние страниц     ¦ ния страниц назначения¦ состояние отображения
          ¦                ¦ DS:SI: массив отображе- менеджера расширенной
          ¦                ¦ ния страниц-источников¦ памяти
          ¦                ¦                       ¦ Состояние отображения
          ¦                ¦                       ¦ менеджера расширенной
          ¦                ¦                       ¦ памяти<--массив отоб-
          ¦                ¦                       ¦ ражения страниц-ис-
          ¦                ¦                       ¦ точников
       15 ¦Получить размер ¦ AX: 4Е03h (код функции) AL: кол-во байтов,
          ¦массива отобра- ¦                       ¦ требуемых для массива
          ¦жения страниц   ¦                       ¦ отображения страниц-
          ¦                ¦                       ¦ источников или страниц
          ¦                ¦                       ¦ назначения
       16 ¦Получить частич-¦ AX: 4F00h (код функции) Массив частичного
          ¦ное отображение ¦ DS:SI-->массив отоб-  ¦ отображения страниц
          ¦страниц         ¦ ражаемых сегментов    ¦ назначения<--частич-
          ¦                ¦ ES:DI-->массив частич-¦ ное состояние отобра-
          ¦                ¦ ного отображения стра-¦ жения менеджера расши-
          ¦                ¦ ниц назначения        ¦ ренной памяти
          ¦                ¦                       ¦
          ¦Структура масси-¦                       ¦
          ¦ва отображаемых ¦                       ¦

                                      - 7-15 -
          ¦сегментов:      ¦                       ¦
          ¦                                        ¦
          ¦mappable_seg_count    dw ?              ¦ Кол-во отображаемых
          ¦                                        ¦ сегм. для сохранения
          ¦mappable_seg_addr dw (mappable_seg_count) Адрес сегмента отоб-
          ¦                  dup (?)               ¦ ражаемых сегментов
          ¦                                        ¦ для сохранения
          ¦Установить час- ¦ AX: 4F01h (код функции) Частичное состояние
          ¦тичное отобра-  ¦ DS:SI-->частичное отоб- отображения менеджера
          ¦жение страниц   ¦ ражение страниц-источ-¦ расширенной памяти<--
          ¦                ¦ ников                 ¦ массив частичного
          ¦                ¦                       ¦ отображения страниц-
          ¦                ¦                       ¦ источников
          ¦Получить размер ¦ AX: 4F02h (код функции) AL: кол-во байтов,
          ¦массива частич- ¦ BX:кол-во страниц в   ¦ необходимое для за-
          ¦ного отображе-  ¦ массиве частичного    ¦ поминания массива
          ¦ния страниц     ¦ отображения страниц   ¦ частичного отображе-
          ¦                ¦                       ¦ ния страниц с указан-
          ¦                ¦                       ¦ ным кол-вом физичес-
          ¦                ¦                       ¦ ких страниц
       17 ¦Отобразить/     ¦ AH: 50h (код функции) ¦ Нет
          ¦Снять отоб-     ¦ AL: 00h - физическая  ¦
          ¦ражение страниц ¦ страница, заданная    ¦
          ¦многих обработ- ¦ своим номером         ¦
          ¦чиков           ¦ 01h - физическая стра-¦
          ¦                ¦ ница, заданная адресом¦
          ¦                ¦ сегмента              ¦
          ¦                ¦ DX: обработчик        ¦
          ¦                ¦ CX: кол-во элементов в¦
          ¦                ¦ массиве отображения   ¦
          ¦                ¦ логических страниц в  ¦
          ¦                ¦ физические            ¦
          ¦                ¦ DS:SI-->массив отобра-¦
          ¦                ¦ жения логических страниц
          ¦                ¦ в физические          ¦
          ¦Структура масси-¦                       ¦
          ¦ва отображения  ¦                       ¦
          ¦логических стра-¦                       ¦
          ¦ниц в физические¦                       ¦
          ¦log_page_number   dw ?                  ¦ Номер логич.страницы
          ¦phys_page_number  dw ?                  ¦ Номер физич.страницы
          ¦                                        ¦ или адрес сегмента, в
          ¦                ¦                       ¦ зависимости от значе-
          ¦                ¦                       ¦ ния, заданного в AL
       18 ¦Переразместить  ¦AH:51h (код функции)   ¦ BX: номера страниц,
          ¦страницы        ¦DX: обработчик         ¦ размещаемых для обра-
          ¦                ¦BX: номера страниц,    ¦ ботки после перераз-
          ¦                ¦которые должны быть у  ¦ мещения
          ¦                ¦обработчика после пе-  ¦
          ¦                ¦реразмещения           ¦
       19 ¦Получить  атри- ¦AX:5200h(код функции)  ¦ AL: 0 - обработчик
          ¦буты обработчи- ¦DX: обработчик         ¦ изменчивый
          ¦ка              ¦                       ¦ 1 - неизменчивый
          ¦Установить ат-  ¦AX:5201h(код функции)  ¦ Нет
          ¦рибуты  обра-   ¦DX: обработчик         ¦
          ¦ботчика         ¦BL: новые  атрибуты    ¦
          ¦                ¦обработчика            ¦

                                      - 7-16 -
          ¦                ¦00h, изменчивый        ¦
          ¦                ¦01h, неизменчивый      ¦
          ¦Получить воз-   ¦AX:5202h(код функции)  ¦ AL: 0 - неизменчи-
          ¦можности по     ¦                       ¦ вость не поддержива-
          ¦атрибутам       ¦                       ¦ ется
          ¦                ¦                       ¦ 1 - поддерживается
       20 ¦Получить имя    ¦AX:5300h(код функции)  ¦ Приемный буфер имени
          ¦обработчика     ¦DX:обработчик          ¦ обработчика<--имя
          ¦                ¦ЕS:DI-->8-символьный   ¦ обработчика
          ¦                ¦буфер-приемник имени   ¦
          ¦                ¦обработчика            ¦
          ¦                ¦                       ¦
          ¦Установить имя  ¦AX:5301h(код функции)  ¦ Буфер-источник имени
          ¦обработчика     ¦DX:обработчик          ¦ обработчика-->имя
          ¦                ¦DS:SI-->8-символьный   ¦ обработчика
          ¦                ¦буфер-источник имени   ¦
          ¦                ¦обработчика            ¦
       21 ¦Получить ката-  ¦AX:5400h(код функции)  ¦ AL:кол-во элементов в
          ¦лог обработчика ¦ES:DI-->массив катало- ¦ массиве каталога обра-
          ¦                ¦га обработчика         ¦ ботчика
          ¦Каталог обработчика:                    ¦
          ¦handle_value     dw ?                   ¦ Активный обработчик
          ¦handle_name      db 8 dup (?)           ¦ Имя обработчика
          ¦Поиск поимено-  ¦AX:5401h(код функции)  ¦ DX: обработчик с ука-
          ¦ванного обра-   ¦DS:SI-->8-символьный   ¦ занным именем
          ¦ботчика         ¦буфер поиска имени     ¦
          ¦                ¦обработчика            ¦
          ¦Получить общее  ¦AX:5402h(код функции)  ¦ BX:общее кол-во
          ¦кол-во обработ- ¦                       ¦ обработчиков, под-
          ¦чиков           ¦                       ¦ держживаемых менедже-
          ¦                ¦                       ¦ ром расширенной памяти
       22 ¦Изменить отоб-  ¦AH:55h (код функции)   ¦ Нет
          ¦ражение страниц ¦AL:0 - физические      ¦
          ¦и перейти       ¦страницы, определенные ¦
          ¦                ¦физическими номерами   ¦
          ¦                ¦страниц                ¦
          ¦                ¦1 - физические страницы¦
          ¦                ¦определенные адресом   ¦
          ¦                ¦сегмента               ¦
          ¦                ¦DX: обработчик         ¦
          ¦                ¦DS:SI-->структура отобра-
          ¦                ¦жения и перехода       ¦
          ¦Структура отоб- ¦                       ¦
          ¦ражения и перехода                      ¦
          ¦target_address   dd ?                   ¦ Точка входа цели
          ¦                ¦                       ¦
          ¦log_phys_map_len db ?                   ¦ Кол-во элементов в
          ¦                                        ¦ структуре отображения
          ¦                ¦                       ¦ страниц-->структуру
          ¦                ¦                       ¦ массива отображения
          ¦                ¦                       ¦ логических страниц
          ¦                ¦                       ¦ в физические, как в
          ¦                ¦                       ¦ функции 17
          log_phys_map_ptr  dd ?                   ¦
       23 ¦Изменить отобра- AH:56h (код функции)   ¦ Нет
          ¦жение страниц и ¦AL:0 - физические      ¦ 1 - физические страни-
          ¦вызвать         ¦страницы, определенные ¦ цы, определенные ад-

                                      - 7-17 -
          ¦                ¦физическими номерами   ¦ ресом сегмента
          ¦                ¦страниц                ¦ DX: обработчик
          ¦                ¦                       ¦ DS:SI-->структура
          ¦                ¦                       ¦ отображения и вызова
          ¦Структура отобра¦                       ¦
          ¦жения и вызова  ¦                       ¦
          ¦target_address   dd ?                   ¦ Удаленный-->точку
          ¦                ¦                       ¦ входа цели
         new_page_map_len   db ?                   ¦ Кол-во страниц для
          ¦                ¦                       ¦ отображения после
          ¦                ¦                       ¦ удаленного вызова
          ¦                ¦                       ¦ -->как в функции 17
         new_page_map_ptr   dd ?                   ¦
         old_page_map_len   db ?                   ¦ Кол-во страниц для
          ¦                ¦                       ¦ отображения после
          ¦                ¦                       ¦ удаленного возврата
          ¦                ¦                       ¦ -->как в функции 17
         old_page_map_ptr   dd ?                   ¦
          ¦Зарезервировано  dw 4 dup (?)           ¦ Зарезервировано для
          ¦                ¦                       ¦ менеджера  расширен-
          ¦                ¦                       ¦ ной памяти
          ¦                ¦                       ¦
          ¦Получить размер ¦AX:5602h(код функции)  ¦ BX:кол-во байт про-
          ¦пространства    ¦                       ¦ странства стека,
          ¦стека отображе- ¦                       ¦ требуемое функцией:
          ¦ния страниц     ¦                       ¦ Изменить отображение
          ¦                ¦                       ¦ страниц и вызвать
       24 ¦Передвинуть об- ¦AX:5700h(код функции)  ¦ Нет
          ¦ласть памяти    ¦DS:SI-->дескриптор об- ¦
          ¦                ¦ласти источника/прием- ¦
          ¦                ¦ника                   ¦
          ¦Обменять об-    ¦AX:5701h(код функции)  ¦ Нет
          ¦ласть памяти    ¦DS:SI-->дескриптор об- ¦
          ¦                ¦ласти источника/прием- ¦
          ¦                ¦ника                   ¦
          ¦Дескриптор об-  ¦                       ¦
          ¦ласти источни-  ¦                       ¦
          ¦ка/приемника    ¦                       ¦
          ¦region_length    dd ?                   ¦ Кол-во байтов для
          ¦                ¦                       ¦ движения/обмена
        source_memory_type  db ?                   ¦ Обычная память: 0
          ¦source_handle    dw ?                   ¦ Обычная память: 0
          ¦                ¦                       ¦ Расширенная память:
          ¦                ¦                       ¦ обработчик источника
        source_init_offset  dw ?                   ¦ Обычная память:
          ¦                ¦                       ¦ начальное смещение в
          ¦                ¦                       ¦ сегменте источника
          ¦                ¦                       ¦ Расширенная память:
          ¦                ¦                       ¦ начальное смещение в
          ¦                ¦                       ¦ странице источника
        source_page_seg     dw ?                   ¦ Обычная память:
          ¦                ¦                       ¦ начальный сегмент
          ¦                ¦                       ¦ источника
          ¦                ¦                       ¦ Расширенная память:
          ¦                ¦                       ¦ начальная логическая
          ¦                ¦                       ¦ страница источника
        dest_memory_type    db ?                   ¦ Обычная память: 0

                                      - 7-18 -
          ¦                                        ¦ Расширенная память:1
          ¦  dest_handle   ¦dw ?                   ¦ Обычная память: 0
          ¦                ¦                       ¦ Расширенная память:
          ¦                ¦                       ¦ обработчик приемника
          dest_init_offset  dw ?                   ¦ Обычная память: на-
          ¦                ¦                       ¦ чальное смещение в
          ¦                ¦                       ¦ сегменте приемника
          ¦                ¦                       ¦ Расширенная память:
          ¦                ¦                       ¦ начальное смещение в
          ¦                ¦                       ¦ странице приемника
          dest_seg_page     dw ?                   ¦ Обычная память: на-
          ¦                ¦                       ¦ чальный сегмент при-
          ¦                ¦                       ¦ емника
          ¦                ¦                       ¦ Расширенная память:
          ¦                ¦                       ¦ начальная логическая
          ¦                ¦                       ¦ страница приемника
       25 ¦Получить массив ¦AX:5800h(код функции)  ¦ CX: кол-во элементов
          ¦отображаемых    ¦ES:DI-->массив отоб-   ¦ в массиве отобража-
          ¦физических      ¦ражаемых физических    ¦ емых физических ад-
          ¦адресов         ¦адресов                ¦ ресов
          ¦Массив отобра-  ¦(Массив, отсортирован- ¦
          ¦жаемых физичес- ¦ный по порядку нарас-  ¦
          ¦ких адресов     ¦тания сегментов)       ¦
         phys_page_segment  dw ?                   ¦ Адрес сегмента отоб-
          ¦                ¦                       ¦ ражаемой страницы,
          ¦                ¦                       ¦ соответствующей но-
          ¦                ¦                       ¦ меру физической
          ¦                ¦                       ¦ страницы
         phys_page_number   dw ?                   ¦
          ¦Получить счетчик¦AX:5801(код функции)   ¦ CX: кол-во элементов
          ¦элементов масси-¦                       ¦ в массиве отображае-
          ¦ва физических   ¦                       ¦ мых физических адре-
          ¦адресов         ¦                       ¦ сов
          ¦                ¦                       ¦
       26 ¦Получить массив ¦AX:5900(код функции)   ¦ Массив конфигурации
          ¦конфигурации    ¦ES:DI-->массив конфи-  ¦ оборудования<--дан-
          ¦оборудования    ¦гурации оборудования   ¦ ные оборудования
          ¦Массив конфигу- ¦                       ¦
          ¦рации оборудования                      ¦
          ¦raw_page_size    dw ?                   ¦ Исходный размер
          ¦                ¦                       ¦ страницы в байтах
         alternate_reg_sets dw ?                   ¦ Кол-во альтернатив-
          ¦                ¦                       ¦ ных наборов регист-
          ¦                ¦                       ¦ ров отображения
          ¦save_area_size   dw ?                   ¦ Кол-во байтов в об-
          ¦                ¦                       ¦ ласти сохранения
          ¦                ¦                       ¦ контекста(также воз-
          ¦                ¦                       ¦ вращается функц. 15)
          ¦DMA_reg_sets     dw ?                   ¦ Кол-во наборов ре-
          ¦                ¦                       ¦ гистров, которое мо-
          ¦                ¦                       ¦ жет назначаться ка-
          ¦                ¦                       ¦ налам ПДП
          ¦                ¦                       ¦ 0: работа ПДП по
          ¦                ¦                       ¦ стандарту LIM
         DMA_channel_op     dw ?                   ¦ 0: работа ПДП по
          ¦                ¦                       ¦ стандарту LIM
          ¦                ¦                       ¦ 1: только 1 канал ПДП

                                      - 7-19 -
          ¦Получить счетчик¦AX:5902h(код функции)  ¦ BX: кол-во неразме-
          ¦неразмещенных   ¦                       ¦ щенных исходных
          ¦исходных страниц¦                       ¦ страниц
          ¦                ¦                       ¦ DX: общее кол-во
          ¦                ¦                       ¦ исходных страниц
       27 ¦Разместить      ¦AH:5Аh (код функции)   ¦ DX: обработчик
          ¦стандартные/    ¦AL:00h-разместить      ¦ исходных/стандарт-
          ¦исходные страни-¦стандартные страницы   ¦ ных страниц
          ¦цы              ¦01h-разместить исход-  ¦
          ¦                ¦ные страницы           ¦
          ¦                ¦BX:нет страниц для раз-¦
          ¦                ¦мещения                ¦
       28 ¦Получить альтер-¦AX:5В00h(код функции)  ¦ Если BL<>0<--актив-
          ¦нативный набор  ¦                       ¦ ный альтернативный
          ¦регистров отоб- ¦                       ¦ набор регистров
          ¦ражения         ¦                       ¦ отображения
          ¦                ¦                       ¦ Если BL=0-ES:DI<--
          ¦                ¦                       ¦ область сохранения
          ¦                ¦                       ¦ контекста регистров
          ¦                ¦                       ¦ отображения
          ¦Установить аль- ¦AX:5В01h(код функции)  ¦ Нет
          ¦тернативный на- ¦BL:00h                 ¦
          ¦бор регистров   ¦ES:DI-->область сохра- ¦
          ¦отображения     ¦нения контекста регист-¦
          ¦                ¦ров отображения        ¦
          ¦                ¦<>00h-кол-во альтерна- ¦
          ¦                ¦тивных наборов  регист-¦
          ¦                ¦ров отображения        ¦
          ¦Получить размер ¦AX:5В02h(код функции)  ¦ DX: кол-во байтов в
          ¦области сохра-  ¦                       ¦ области сохранения
          ¦нения альтерна- ¦                       ¦ контекста регистров
          ¦тивного отобра- ¦                       ¦ отображения
          ¦жения           ¦                       ¦
          ¦Разместить аль- ¦AX:5В03h(код функции)  ¦ BL:0 - нет доступных
          ¦тернативный на- ¦                       ¦ альтернативных набо-
          ¦бор регистров   ¦                       ¦ ров регистров отоб-
          ¦отображения     ¦                       ¦ ражения
          ¦                ¦                       ¦ <>0 - кол-во разме-
          ¦                ¦                       ¦ щенных альтернатив-
          ¦                ¦                       ¦ ных наборов регист-
          ¦                ¦                       ¦ ров отображения
          ¦Освободить аль- ¦AX:5В04h(код функции)  ¦ Нет
          ¦тернативный на- ¦BL: кол-во альтернатив-¦
          ¦бор регистров   ¦ных наборов регистров  ¦
          ¦отображения     ¦отображения            ¦
          ¦                ¦                       ¦
          ¦Разместить на-  ¦AX:5В05h(код функции)  ¦ BL:0 - наборы регист-
          ¦бор регистров   ¦                       ¦ ров ПДП не поддержи-
          ¦ПДП             ¦                       ¦ ваются
          ¦                ¦                       ¦ <>0 - кол-во разме-
          ¦                ¦                       ¦ щенных наборов
          ¦                ¦                       ¦ регистров ПДП
          ¦Включить ПДП на ¦AX:5В06h(код функции)  ¦ Нет
          ¦альтернативный  ¦BL:номер набора регист-¦
          ¦набор регистров ¦ров ПДП                ¦
          ¦отображения     ¦DL: номер канала ПДП   ¦
          ¦Выключить ПДП на¦AX:5В07h(код функции)  ¦ Нет

                                      - 7-20 -
          ¦альтернативный  ¦BL:номер набора регист-¦
          ¦набор регистров ¦ров ПДП                ¦
          ¦отображения     ¦                       ¦
          ¦Освободить на-  ¦AX:5В08h(код функции)  ¦ Нет
          ¦бор регистров   ¦BL:номер набора регист-¦
          ¦ПДП             ¦ров ПДП                ¦
       29 ¦Подготовиться   ¦AH:5Сh (код функции)   ¦ Нет
          ¦к загрузке из   ¦                       ¦
          ¦памяти          ¦                       ¦
       30 ¦Включить набор  ¦AX:5D00h(код функции)  ¦ BX,CX: ключ доступа
          ¦функций OS/E    ¦BX,CX: ключ доступа    ¦ (возвращаемый толь-
          ¦                ¦(требующийся при всех  ¦ ко на первом вызове)
          ¦                ¦вызовах после первого) ¦
          ¦Выключить набор ¦AX:5D01h(код функции)  ¦ BX,CX: ключ доступа
          ¦функций OS/E    ¦BX,CX: ключ доступа    ¦ (возвращаемый толь-
          ¦                ¦(требующийся при всех  ¦ ко на первом вызове)
          ¦                ¦вызовах после первого) ¦
          ¦Возвратить ключ ¦AX:5D02h(код функции)  ¦ Нет
          ¦доступа         ¦BX,CX: ключ доступа    ¦
          ¦                ¦(возвращаемый первым   ¦
          ¦                ¦вызовом включения или  ¦
          ¦                ¦выключения набора функ-¦
          ¦                ¦ций OS/E               ¦
       ДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДД


                      Реализации менеджера расширенной памяти

              Исходный менеджер  расширенной  памяти был разработан фирмой
         Intel для обеспечения программного интерфейса с  платами  памяти,
         которые  изготавливались для спецификации расширенной памяти LIM.
         Другие поставщики делали менеджеры  расширенной  памяти,  которые
         подгонялись  к платам расширенной памяти их собственного изготов-
         ления. Однако, реализации менеджера расширенной памяти не ограни-
         чены  по  форме программного драйвера для специального типа платы
         памяти.
              В предыдущем рассмотрении менеджера расширенной памяти отме-
         чалось,  что спецификация расширенной памяти LIM (по крайней мере
         после  версии 3.2) в сущности не зависит от аппаратуры. Несколько
         разработчиков программного обеспечения учли этот факт и продолжа-
         ли создавать менеджеры расширенной памяти, которые не требуют ни-
         какого специального оборудования расширенной памяти. Эти менедже-
         ры расширенной памяти,  на которые обычно ссылаются как на эмуля-
         торы или имитаторы расширенной памяти, моделируют расширенную па-
         мять путем своппинга данных из/на обычной памяти на  диск  или  в
         расширенную память,  присутствующие на многих системах, совмести-
         мых с PC/AT.
              Появление систем,  совместимых с PC/AT и PS/2,  которые  ис-
         пользуют  микропроцессор Intel 80386,  сделало возможным создание
         менеджера расширенной памяти  другого  типа,  который  использует
         усовершенствованную аппаратуру управления памятью, существующую в
         каждом 80386.  Фирма COMPAQ в настоящее время включает  в  каждую
         свою модель Deskpro 386 менеджер расширенной памяти, базирующийся
         на этой возможности, именуемый CEMM.
              В то  время  как каждый вариант менеджера расширенной памяти
         реализует спецификацию расширенной памяти LIM,  каждый тип  также
         характеризуется набором показателей стоимости, производительности

                                      - 7-21 -
         и затрат по обеспечению совместимости,  которые важны для Вас как
         для  разработчика приложения спецификации расширенной памяти. Вам
         требуется знать соображения разработки и программирования,  кото-
         рые  будут  влиять на способность Вашего программного обеспечения
         правильно функционировать  с  приемлемой  производительностью  на
         различных системах расширенной памяти.  Если стоимость разработки
         является главным фактором,  например,  вложение средств в относи-
         тельно  недорогую программу имитатора расширенной памяти позволит
         Вам создавать, тестировать и выполнять программы, которые пользу-
         ются  расширенной памятью без вложения сотен долларов в новую ап-
         паратуру.
              В следующих  разделах  описываются  конкретные достоинства и
         недостатки, связанные с каждым типом реализации расширенной памя-
         ти, по отношению к совместимости, производительности и стоимости.

           Оборудование и  программное  обеспечение  расширенной памяти

              Вне зависимости от конкретных подробностей конструкции платы
         менеджер расширенной  памяти управляет динамическим переключением
         памяти в и из прямо адресуемую память через набор регистров отоб-
         ражения на плате. Регистры отображения реализуются последователь-
         ностью портов ввода/вывода,  где-то в  пространстве  ввода/вывода
         компьютера. Плата и поддерживающий ее менеджер расширенной памяти
         должны конфигурироваться при установке для приспособления к поль-
         зованию  адресного  пространства ввода/вывода и адресов выше 640К
         адаптерами видео и ввода/вывода, а также ПЗУ BIOS.
                                    Достоинства
              Скорость - можно отображать страницу  расширенной  памяти  в
         страничный  кадр  спецификации расширенной памяти за примерно 100
         микросекунд.
              Широкая доступность - доступны платы для систем с шинной ар-
         хитектурой PC, PC/AT и Microchannel.
                                    Недостатки
              Дорого - платы расширенной памяти большого объема могут сто-
         ить столько, сколько целый компьютер.
              Пространство - требуется одно или более гнезд шины  в  блоке
         компьютера.
              Совместимость - не каждая плата спецификации расширенной па-
         мяти  будет работать в каждой компьютерной системе.  Пользователи
         высокопроизводительных PC/AT должны обращать внимание,  чтобы по-
         купать платы спецификации расширенной памяти,  способные работать
         на скорости шины их системы.

                    Аппаратура и программное обеспечение 80386

              Возможно использовать оборудование управления памятью,  при-
         сутствующее в каждом микропроцессоре 80386,  в сочетании с вирту-
         альным режимом 8086,  имеющимся в 80386. Детали работы управления
         памятью 80386 и режима виртуального 8086 слишком сложны для  объ-
         яснения здесь.  Достаточно сказать, что их хватает для выполнения
         функций отображения, обслуживаемых регистрами отображения страниц
         на сложной плате расширенной памяти.
                                    Достоинства
              Скорость -  отображения страниц можно выполнять за несколько
         микросекунд.
              Недорого - предполагается, что у Вас уже есть машина 80386 с
         большим количеством расширенной памяти.

                                      - 7-22 -
                                    Недостатки
              Дорого -  предполагается,  что  у Вас еще нет машины на базе
         80386 или что у нее нет большого объема расширенной памяти.  Сов-
         местимость  - другое программное обеспечение,  использующее защи-
         щенный режим 80386,  такое как некоторые многозадачные среды  или
         "Расширители DOS",  может конфликтовать с использованием менедже-
         ром расширенной памяти  средств  защищенного  режима,  таких  как
         страничный режим и виртуальный режим 8086. Тестирование менеджера
         расширенной памяти 80386 с фактическим  сочетанием  аппаратуры  и
         программного обеспечения,  с которым он будет применяться,  может
         быть единственным способом, гарантирующим успех.

                          Только программное обеспечение

              На ЦВМ на базе 8086 менеджер  расширенной  памяти  эмулирует
         память  спецификации  расширенной  памяти  путем своппинга данных
         между кадром страниц,  расположенным в обычной памяти,  и  гибким
         или  жестким  диском.  На ЦВМ на базе 80286 с расширенной памятью
         (совместимые с PC/AT и модели 50 и 60 PS/2)  менеджер расширенной
         памяти может эмулировать расширенную память путем своппинга стра-
         ниц спецификации расширенной памяти между расширенной  памятью  и
         кадром  страниц спецификации расширенной памяти в обычной памяти.
                                    Достоинства
              Недорого - менеджер расширенной памяти требует только те ре-
         сурсы, которые обычно в наличии на базовом оборудовании.
                                    Недостатки
              Производительность - перенос данных из обычной памяти в рас-
         ширенную и обратно занимает в десятки и сотни раз больше времени,
         чем при отображении страниц на настоящей плате расширенной  памя-
         ти. Для жесткого диска требуется в сотни и тысячи раз больше вре-
         мени.  Если все, чем Вы обладаете - гибкий диск, потребуется при-
         мерно в миллион раз больше времени.  Возможно удивительно, однако
         существуют определенные приложения памяти спецификации  расширен-
         ной памяти, которые не требуют производительности платы расширен-
         ной памяти,  такие как сохранение текста или графических  экранов
         для последующего повторного вызова или своппинг TSR в и из памяти
         по запросу.
                                   Совместимость
              На настоящей плате (или  эмуляторе  типа  80386)  логическая
         страница  спецификации  расширенной памяти может отображаться бо-
         лее,  чем в одну физическую страницу спецификации расширенной па-
         мяти в один и тот же момент времени, с помощью метода, именуемого
         совмещение имен (который будет рассмотрен подробнее далее в  гла-
         ве).  Поскольку имитаторы копируют страницы памяти, а не "отобра-
         жают" их в различные области адресного  пространства, приложения,
         которые полагаются на совмещение данных,  не могут работать с та-
         ким типом менеджера расширенной памяти.  На практике  большинство
         приложений,  которые  используют спецификацию расширенной памяти,
         не полагаются на совмещение для работы.

                      IBM PS/2 80286 опция увеличенной памяти
              Кратко документированное  средство  опции увеличенной памяти
         IBM 80286 для моделей 50 и 60 PS/2 обеспечивает  набор  регистров
         субадресации, доступный через средство выбора программируемой оп-
         ции архитектуры шины микроканала, которое может быть запрограмми-
         ровано,  для того чтобы работать подобно регистрам отображения на
         настоящей плате расширенной памяти.

                                      - 7-23 -
              Очевидно, эти  регистры  субадресации  были  спроектированы,
         чтобы позволить программам самотестирования при включении питания
         в ПЗУ BIOS переотображать любые отказавшие физические блоки памя-
         ти на старшие адреса памяти (и отображать хорошие блоки на прост-
         ранство, оставшееся после этого в младших адресах), так чтобы ма-
         шина могла работать,  даже после отказа одной или нескольких  БИС
         памяти.
              Также возможно отображение памяти платы увеличенной памяти в
         пространство  ниже  640К  для предоставления отображаемой обычной
         памяти, разрешаемое усовершенствованной спецификацией расширенной
         памяти AQA и спецификацией расширенной памяти LIM 4.0.  К сожале-
         нию,  целый мегабайт памяти,  присутствующей на планарных  платах
         моделей 50 и 60 PS/2, должен быть при этом отключен.
                                    Достоинства
              Недорого - подразумевается, что у Вас уже есть модели 50 или
         60 IBM PS/2 с опцией увеличения расширенной  памяти  IBM.  Другие
         платы  увеличения  памяти для PS/2 могут поддерживать или не под-
         держивать средство субадресации.
              Производительность - менеджер расширенной памяти, написанный
         для пользования этими регистрами,  может точно также работать  на
         сложной плате расширенной памяти.
                                    Недостатки
              Дорого -  подразумевается,  что  у Вас еще нет PS/2 с опцией
         увеличения расширенной памяти IBM.

          Интерфейс прикладной программы спецификации расширенной памяти

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

                 Программирование на языке ассемблера спецификации
                                расширенной памяти

              Программисты на языке  ассемблера,  привыкшие  к  интерфейсу
         системных  вызовов DOS,  найдут,  что менеджер расширенной памяти
         представляет практически идентичную ситуацию:
              1. Поместить  код функции для требующейся функции специфика-
         ции расширенной памяти в регистр AH.
              2. Поместить  другие  аргументы,  необходимые  для выбранной
         функции, и/или структуры данных в память, как определено специфи-
         кацией расширенной памяти LIM.
              3. Передать управление менеджеру  расширенной  памяти  путем
         выдачи программного прерывания 67h.
              4. Менеджер расширенной памяти возвращает  управление  прог-
         рамме,  выдавшей запрос,  перезаписывая код функции, помещенный в
         регистр AH на шаге, кодом состояния для запрошенной операции. Код
         состояния 00h сигнализирует об успешном завершении функции; любое
         другое значение показывает,  что менеджер расширенной памяти  на-
         толкнулся  на  какие-либо  проблемы,  пытаясь выполнить выбранную
         функцию.  Значения кодов ошибок и их смысл  перечислены  далее  в
         этой главе.


                                      - 7-24 -
                                Конфликт прерываний

              Программисты должны знать,  что прерывание 67h не зарезерви-
         ровано "официально" для спецификации расширенной памяти LIM; мно-
         гие другие доступные в продаже программы  также  используют  его.
         Конфликты в использовании этого прерывания часто озадачивают раз-
         работчиков и пользователей программного  обеспечения спецификации
         расширенной памяти,  когда приложение,  которое работает на одной
         машине,  перестает работать на другой с идентичной  конфигурацией
         оборудования.  Возможно  для прерывания 67h,  чтобы оно совместно
         разделялось более, чем одной программой, хотя менеджеры расширен-
         ной  памяти не программируются для выполнения этого.  Даже,  если
         менеджер расширенной памяти способен разделять использование пре-
         рывания 67h с другим программным обеспечением, один из двух доку-
         ментированных методов обнаружения присутствия менеджера расширен-
         ной памяти может не сработать,  если другой обработчик прерывания
         встанет в цепь перед обработчиком прерывания менеджера  расширен-
         ной памяти.
              Поскольку применение прерывания 67h встроено  в спецификацию
         расширенной памяти LIM,  любое приложение, написанное для исполь-
         зования расширенной памяти,  связывается с менеджером расширенной
         памяти  путем  выдачи  этого прерывания. Таким образом, конфликты
         из-за прерывания 67h между менеджером расширенной памяти и други-
         ми  программами  могут  быть  разрешены только,  если программное
         обеспечение,  не относящееся к спецификации  расширенной  памяти,
         может быть реконфигурировано или модифицировано,  чтобы использо-
         вать другой вектор прерывания.

                               Языки высокого уровня

              Как и  в  случае  с  системными вызовами DOS,  не существует
         стандартного интерфейса между языками высокого уровня, такими как
         Си, Паскаль  или  ФОРТРAH и спецификацией расширенной памяти LIM.
         Однако, разработчики, которые желают обращаться к расширенной па-
         мяти из приложений,  написанных на языках высокого уровня, обычно
         имеют несколько жизнеспособных альтернатив.  В  продаже  доступны
         несколько  библиотек  функций спецификации расширенной памяти для
         некоторых различных языков.  Многие популярные  языковые  системы
         обладают подпрограммами или функциями, которые обеспечивают общие
         средства для доступа к регистрам микропроцессора и  выдачи  прог-
         раммных прерываний. Кроме того, языки высокого уровня должны так-
         же обеспечивать какой-либо способ для определения удаленных  ука-
         зателей (регистр сегмента плюс смещение) для  адресации  структур
         данных, передаваемых для менеджера расширенной памяти.
              Программисты, знающие язык ассемблера 808х, знакомые со свя-
         зыванием подпрограмм и соглашениями о передаче параметров их язы-
         ка высокого уровня,  найдут,  что несложно создать набор программ
         сопряжения для спецификации расширенной памяти. Пример такого на-
         бора, написанного на языке Си фирмы Microsoft версия 5.0,  приво-
         дится в конце данной главы вместе с другими программами.

                             Обработка условий ошибок

              Каждый программист, пытающийся написать надежное программное
         обеспечение,  должен ожидать, что придется обрабатывать неизбежно
         появляющиеся ошибки.  Спецификация расширенной памяти LIM 4.0 оп-
         ределяет 36 различных условий ошибок,  которые  может  возвращать

                                      - 7-25 -
         менеджер расширенной памяти, так же как и код, который сигнализи-
         рует об успешном завершении функции. Листинг 7-1 содержит опреде-
         ления для кодов ошибок спецификации расширенной памяти LIM 4.0, а
         в табл. 7-3 перечислены коды состояний ошибок.
              Менеджер расширенной  памяти возвращает детализированный код
         состояния завершения в регистре AH для каждого запроса на  обслу-
         живание при возврате из прерывания.  По отношению к обнаружению и
         сообщению об ошибках интерфейс программирования спецификации рас-
         ширенной памяти более последовательный и менее сложный,  чем DOS.
         Для обнаружения и сообщения условия  ошибки,  последовавшего  при
         вызове DOS,  программист должен проанализировать регистр или флаг
         переноса и затем выдать другой системный  вызов  для  возвращения
         детализированного кода ошибки.
                                                         Таблица 7-3
                  Коды состояний спецификации расширенной памяти
                             Lotus/Intel/Microsoft 4.0
         ДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Код ¦                         Описание
         ДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         00H ¦Указанная функция завершилась без ошибок
         80H ¦Отказ программного обеспечения драйвера менеджера расширенной
             ¦памяти
         81H ¦Драйвер менеджера расширенной памяти  обнаружил  аппаратурный
             ¦отказ
         82H ¦Драйвер менеджера расширенной памяти занят (ничего другого не
             ¦произошло)
         83H ¦Hельзя отыскать указанного обработчика
         84H ¦Код функции неопределен
         85H ¦В настоящий момент нет доступных обработчиков
         86H ¦Произошла ошибка восстановления контекста отображения
         87H ¦Для запроса недостаточно общего числа страниц
         88H ¦Для запроса недостаточно числа неразмещенных страниц
         89H ¦Был запрос на нуль логических страниц из функции, совместимой
             ¦с LIM 3.2
         8AH ¦Логическая страница вне диапазона указанного обработчика
         8BH ¦Физическая страница вне диапазона
         8CH ¦Переполнение области сохранения контекста регистров отображе-
             ¦ния
         8DH ¦У стека контекста регистров отображения  уже  есть  контекст,
             ¦связанный с указанным обработчиком
         8EH ¦У стека контекста регистров отображения нет контекста, связан-
             ¦ного с указанным обработчиком
         8FH ¦Была запрошена неопределенная подфункция
         90H ¦Тип атрибута не определен
         91H ¦Система не поддерживает неразрушаемость (энергонезависимость)
         92H ¦При  передвижении  области произошла частичная перезапись ис-
             ¦точника
         93H ¦Область  расширенной памяти слишком велика для указанного об-
             ¦работчика
         94H ¦Области обычной и расширенной памяти перекрываются
         95H ¦Смещение в пределах логической страницы превосходит длину ло-
             ¦гической страницы
         96H ¦Длина области превышает предел в 1 Мбайт
         97H ¦Области расширенной памяти источника и приемника имеют один и
             ¦тот же обработчик и перекрываются
         98H ¦Hеопределенные/неподдерживаемые  типы памяти источника и при-
             ¦емника

                                      - 7-26 -
         9AH ¦Указанный  альтернативный  набор регистров отображения не су-
             ¦ществует
         9BH ¦Все альтернативные наборы регистров  отображения/ПДП исполь-
             ¦зуются
         9CH ¦Hе поддерживаются альтернативные наборы регистров отображения
             ¦/ПДП
         9DH ¦Указанный  альтернативный  набор регистров отображения/ПДП не
             ¦определен, не размещен или является текущим
         9EH ¦Закрепленные каналы ПДП не поддерживаются
         9FH ¦Указанный закрепленный канал ПДП не существует
         A0H ¦Hе  может быть найдено значение обработчика, соответствующее
             ¦указанному имени обработчика
         A1H ¦Обработчик с указанным именем уже существует
         A2H ¦Попытка перехода к началу 1-Мбайтного  адресного пространства
             ¦во время передвижения или обмена
         A3H ¦Содержимое  структуры  данных  пользователя,  переданное  для
             ¦функции искажено или бессмысленно
         A4H ¦Операционная система не допускает обращения к функции
         ДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                               Листинг 7-1.EMMERR.H
         ------------------------------------------------------------------

         /*

         Продукт:       Диск над;
         Версия:        2.00
         Наименование:  emmerr.h
         Содержание: определения кодов ошибок спецификации расширенной па-
                        мяти LIM 4.0
         Ссылка: спецификация расширенной памяти LIM версия 4.0 стр.А5-А10
         */

        #defineFRSTEMERR  0x80  /*номер первой ошибки менеджера расширенной
                                                           памяти*/
        #defineLASTEMERR  0xA4  /*номер последней ошибки менеджера расши-
                                                          ренной памяти*/

        #defineFUNCCOC    0x00  /*указанн. функция завершилась без ошибок*/
        #defineEMDRVSWF   0x80  /*отказ  программного  обеспечения драйвера
                                             менеджера расширенной памяти*/
        #defineEMDRVHWF   0x81  /*драйвер менеджера расширенной памяти
                                             обнаружил аппаратурный отказ*/
        #defineEMDRVBSY   0x82  /*драйвер менеджера расширенной памяти
                                      занят (ничего другого не произошло)*/
        #defineHANDLNFD   0x83  /*нельзя отыскать указанного обработчика */
        #defineFUNCCUND   0x84  /*код функции неопределен */
        #defineHANDLINS   0x85  /*в настоящий момент нет доступных обра-
                                                                ботчиков*/
        #defineMAPCXPRO   0x86  /*произошла ошибка восстановления контекста
                                                              отображения*/
        #defineTOTPGINS   0x87  /*для запроса   недостаточно   общего
                                                           числа  страниц*/
        #defineUNAPGINS   0x88  /*для  запроса недостаточно числа
                                                    неразмещенных страниц*/
        #defineLPAGE2SM   0x89  /*был запрос на нуль логических страниц из
                                          функции,  совместимой с LIM 3.2*/

                                      - 7-27 -
        #defineLPAGERNG   0x8A  /*логическая страница вне диапазона ука-
                                                      занного обработчика*/
        #definePPAGE2BG   0x8B  /*физическая страница вне диапазона*/
        #defineMRCSAFUL   0x8C  /*переполнение области сохранения контекста
                                                   регистров  отображения*/
        #defineMRCSTDUP   0x8D  /*у  стека  контекста регистров отображения
                                уже есть контекст, связанный  с  указанным
                                                             обработчиком*/
        #defineMRCSTNFD   0x8E  /*у стека контекста регистров отображения
                                нет контекста, связанного с указанным об-
                                                               работчиком*/
        #defineSFUNCUND   0x8F  /*была запрошена неопределенн. подфункция*/
        #defineATTRBUND   0x90  /*тип атрибута не определен*/
        #defineNVSTGUNS   0x91  /*система  не поддерживает неразрушаемость
                                                    (энергонезависимость)*/
        #defineMREGNOVW   0x92  /*при передвижении области произошла
                                           частичная перезапись источника*/
        #defineMREGN2SM   0x93  /*область расширенной памяти слишком велика
                                  для указанного обработчика*/
        #defineMREGNOVL   0x94  /*области обычной и расширенной памяти пе-
                                                           рекрываются*/
        #defineLPGOF2BG   0x95  /*смещение  в пределах логической страницы
                                 превосходит длину логической страницы*/
        #defineMREGN2BG   0x96  /*длина области превышает предел в 1Мбайт*/
        #defineMREGNDUP   0x97  /*области расширенной памяти источника и
                                приемника имеют один и тот же обработчик и
                                                            перекрываются*/
        #defineMREGNUND   0x98  /*неопределенные/неподдерживаемые  типы
                                             памяти источника и приемника*/
        #defineAMRSNFD    0x9A  /*указанный альтернативный набор регистров
                                                отображения не существует*/
        #defineAMDRSINS   0x9B  /*все альтернативные наборы регистров отоб-
                                                 ражения/ПДП используются*/
        #defineAMDRSUNS   0x9C  /*не поддерживаются альтернативные наборы
                                                регистров отображения/ПДП*/
        #defineAMDRSUND   0x9D  /*указанный альтернативный набор регистров
                                отображения/ПДП не определен,  не размещен
                                                     или является текущим*/
        #defineDDMACUNS   0x9E  /*закрепленные каналы ПДП не поддерж-ся*/
        #defineDDMACNFD   0x9F  /*указанный закрепленный канал ПДП не су-
                                                                 ществует*/
        #defineHNDVLNFD   0xA0  /*не может быть найдено значение обработчи-
                                ка, соответствующее  указанному  имени об-
                                                                работчика*/
        #defineHNDNMDUP   0xA1  /*обработчик с указанным именем уже сущест-
                                                                     вует*/
        #defineMREGNWRP   0xA2  /*попытка перехода к началу 1-Мбайтного
                                адресного пространства во время передвиже-
                                                           ния или обмена*/
        #defineUSRDSFMT   0xA3  /*содержимое структуры данных пользователя,
                                переданное для функции  искажено  или бес-
                                                                смысленно*/
        #defineOPSYSACC   0xA4  /*операционная система не допускает обраще-
                                                            ния к функции*/
         -------------------------------------------------------------------



                                      - 7-28 -
         Поскольку  проверка  на  наличие  ошибок,  обнаруженных  монитором
         расширенной памяти  -  просто  вопрос тестирования регистра AH на
         нуль после каждого вызова,  всегда  оказывается  разочаровывающим
         наталкивание  на популярное коммерческое программное обеспечение,
         которое не удосуживается проделать это.  Избавьте себя  (и  Ваших
         пользователей) от забот отлавливания загадочных зависаний и отка-
         зов посредством проверки кода ошибки после каждого вызова  менед-
         жера расширенной памяти.
              Реакция программы на условие ошибки, возвращенное менеджером
         расширенной памяти,  зависит от природы ошибки и  от  возможности
         программы приспособиться к условиям, на которые спецификация рас-
         ширенной памяти LIM ссылается как на "невосстанавливаемые".   На-
         пример, программа может сделать очень немногое, получив от менед-
         жера расширенной памяти индикацию "Отказ  аппаратуры  расширенной
         памяти",  помимо  сообщения о проблеме пользователю и воздержания
         от дополнительного использования обслуживанием  менеджера  расши-
         ренной памяти.
              И, наоборот, адаптирующаяся программа могла бы быть способна
         восстанавливаться  из условий таких,  как "Недостаточно доступных
         страниц спецификации расширенной памяти", возможно, путем исполь-
         зования  дискового  файла в качестве временной области сохранения
         для данных, которые не могут поместиться в расширенной памяти.
              Другие условия такие,  как "Физическая страница вне диапазо-
         на", обычно показывают, что имеет место ошибка проектирования или
         программирования  в  приложении  спецификации расширенной памяти.
         Реализуйте Ваши программы обработки ошибок спецификации расширен-
         ной памяти так,  чтобы они сообщали ячейку в Вашей программе, где
         появилось условие ошибки,  предпочтительно таким образом, который
         связан с исходным кодом.

            Написание программ, которые пользуются расширенной памятью

              Все программы, которые пользуются расширенной памятью, долж-
         ны соблюдать определенный протокол.  Каждая программа должна  вы-
         полнять нижеследующие шаги в том порядке, как они перечислены:
              1. Обнаружить наличие менеджера расширенной памяти.
              2. Определить, есть ли в наличии достаточное для Вашего при-
         ложения количество страниц расширенной памяти.
              3. Получить адрес начала кадра страниц.
              4. Разместить страницы расширенной памяти.
              5. Отобразить страницы расширенной памяти в кадр страниц.
              6. Считывать,  записывать или выполнять данные в расширенной
         памяти.
              7. Возвратить страницы расширенной памяти  менеджеру  расши-
         ренной памяти перед завершением Вашего приложения.

                  Общие руководящие указания по программированию

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

              * Программы,  использующие совмещение данных, должны гаранти-
         ровать, что система расширенной памяти поддерживает это. Совмеще-
         ние данных имеет место,  когда одна логическая страница расширен-

                                      - 7-29 -
         ной памяти отображается более,  чем в  одну  физическую  страницу
         кадра страниц.  В реализациях менеджера расширенной памяти, кото-
         рые используют аппаратуру  отображения  страниц,  эффект  данного
         способа  заключается в том,  что 16-Кбайтная страница расширенной
         памяти будет появляться более,  чем в одном 16-Кбайтном блоке ад-
         ресного  пространства  процессора.  Менеджеры расширенной памяти,
         написанные для истинных плат спецификации расширенной памяти, ап-
         паратурного страничного механизма 80386 и опции расширенной памя-
         ти 80286 IBM PS/2 - все могут поддерживать данный способ. Однако,
         чисто программные эмуляторы спецификации расширенной памяти,  ко-
         торые имитируют отображение страниц путем копирования блоков дан-
         ных в память, не могут выполнять совмещение данных.
              Ваша программа может выполнить следующую проверку  для того,
         чтобы определить поддерживает ли менеджер расширенной памяти сов-
         мещение данных:
              1. Отобразить  одну  логическую страницу,  по крайней мере в
         две физические.
              2. Записать данные в одну из физических страниц.
              3. Если данные,  записанные в физическую страницу на шаге 2,
         также  появляются  в других физических страницах,  в которые была
         отображена логическая страница, тогда реализация менеджера расши-
         ренной памяти поддерживает совмещение данных.
              На рис.  7-2  иллюстрируется  совмещение  данных.  Поскольку
         единственная  логическая  страница отображается в первую и третью
         физические страницы в пределах кадра страниц,  к элементу данных,
         находящемуся по смещению 2132Н в логической странице, можно обра-
         титься по физическим адресам CC00:2132 и D400:2132.

              * Приложения должны возвращать все размещенные  страницы  ме-
         неджеру расширенной  памяти  перед  завершением  программы.  Ваша
         программа должна возвратить каждый обработчик расширенной памяти,
         размещенный Вашей программой,  перед ее нормальным или ненормаль-
         ным завершением.
           Начальный сегмент     N физической
           физической страницы     страницы
                        CC00H    ЪДДДДДДД¬ДД      Элемент данных по
                                 ¦       ¦   \    смещению 2132Н в
                                 ¦   0   ¦     \  логической странице
                        D000H    ГДДДДДДДґД      \ЪДДДДДДДДДДДДДДДДД¬
                                 ¦       ¦ \    / ¦       ЪДДДДДД¬  ¦
                                 ¦   1   ¦   \/   ¦       ¦123.45¦  ¦
                        D400H    ГДДДДДДДґДД/  \  ¦       АДДДДДДЩ  ¦
                                 ¦       ¦      /\АДДДДДДДДДДДДДДДДДЩ
                                 ¦   2   ¦    /
                        D800H    ГДДДДДДДґДД/
                                 ¦       ¦
                                 ¦   3   ¦    Чтение/Запись: физические
                                 АДДДДДДДЩ    адреса CC00:2132 или
                                              D400:2132 будут ссылаться
                                              на один и тот же элемент
                                              данных в расширенной памяти

             Рис. 7-2. Совмещение данных  -  одна логическая страница,
                     отображенная более чем в одну физическую страницу

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

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

              * Любая структура данных,  адрес которой передается в вызове
         функции менеджера расширенной  памяти,  не  должна  находиться  в
         отображаемой памяти. За исключением функций "Изменить отображение
         страницы и перейти" и "Сменить отображение страницы  и  вызвать",
         которые  были специально разработаны для поддержки выполнения ко-
         довых объектов в расширенной памяти, структуры данных, адреса ко-
         торых передаются менеджеру расширенной памяти,  должны находиться
         в памяти, которая не может перестать отображаться. Например, Ваша
         программа  не может запоминать области сохранения контекста отоб-
         ражения в расширенной памяти.

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

           Применение расширенной  памяти  в  нерезидентных  программах

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

                 Обнаружение наличия менеджера расширенной памяти

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

                            Метод открытого обработчика
              Для обнаружения  наличия  менеджера расширенной памяти с по-

                                      - 7-31 -
         мощью метода открытого обработчика используется ряд системных вы-
         зовов  файлов DOS,  для выявления присутствия драйвера устройства
         менеджера расширенной памяти и, в случае его присутствия, для оп-
         ределения его способности обслуживать запросы для прерывания 67h.
         Метод действует следующим образом:
              1. Выполняется вызов открытого обработчика DOS (функция DOS-
         3Dh), определяющий доступ только по чтению (режим 0) с именем пу-
         ти  доступа  EMMXXXX0.  Это - имя драйвера символьного устройства
         менеджера расширенной памяти,  который был  установлен  во  время
         первичной загрузки, если для драйвера устройства менеджера расши-
         ренной памяти в файле CONFIG.SYS было указано DEVICE=элемент.
              2. Если  вызов  открытого обработчика не выполняется с кодом
         возврата "не найдено имя файла или пути доступа",  то  Вы  можете
         полагать, что расширенная память отсутствует. Вызов открытого об-
         работчика может также не выполниться, если все обработчики файлов
         DOS  используются перед тестом наличия.  Для предотвращения этого
         Ваша программа должна выполнять тест наличия  расширенной  памяти
         до открытия любого другого файла.
              3. Если вызов открытого обработчика завершается успешно, это
         показывает, что существует файл или устройство с именем менеджера
         расширенной памяти.  Для того, чтобы установить, относится ли об-
         работчик, возвращенный на шаге 1, к устройству или файлу, выдайте
         вызов "Управление ввода/вывода для устройств" (IOCTL)  -  функция
         DOS  44h - с подфункцией "Получить информацию об устройстве" (ре-
         гистр AL=00h) для обработчика файла, возвращенного на шаге 1.
              4. Если обработчик принадлежит устройству, бит 7 регистра DL
         будет равен 1,  что показывает на присутствие менеджера расширен-
         ной памяти.  Если бит 7 равен 0,  обработчик связан с файлом, так
         что Вы можете полагать, что расширенная память отсутствует.
              5. Если  обработчик  ссылается на устройство,  выдайте вызов
         IOCTL  с  подфункцией  "Получить  выходное  состояние"   (регистр
         AL=07h) для данного обработчика, для того чтобы определить готов-
         ность менеджера расширенной памяти обрабатывать запросы на обслу-
         живание расширенной памяти.
              6. Если  менеджер расширенной памяти готов обрабатывать  за-
         просы обслуживания расширенной памяти, по вызову IOCTL возвратит-
         ся значение 0FFh в регистре AL.  В противном случае менеджер рас-
         ширенной памяти отсутствует или неспособен  обрабатывать  запросы
         на обслуживание расширенной памяти.
              7. Если начальный открытый обработчик DOS достиг цели,  зак-
         ройте его с помощью вызова закрытия обработчика (функция DOS 3h).
         Данный  обработчик  более не нужен,  поскольку дальнейшее общение
         между менеджером расширенной памяти и Вашим приложением  происхо-
         дит  через интерфейс прерывания 67h и не использует файловую сис-
         тему DOS.

                 Проверка версии спецификации расширенной памяти,
                   поддерживаемой менеджером расширенной памяти

              Если менеджер расширенной памяти существует и готов обслужи-
         вать запросы, выдайте функцию 7 "Получить версию", для того чтобы
         удостовериться,  что версия менеджера расширенной памяти, с кото-
         рым Ваша программа сообщается,  поддерживает версию  спецификации
         расширенной  памяти,  которая  требуется Вашей программе.  Данная
         функция возвращает число в двоично-десятичном коде из двух цифр в
         регистре AL.  Старшие четыре бита числа показывают основной номер
         версии.  Младшие четыре бита или дробная часть этого числа  могут

                                      - 7-32 -
         использоваться  поставщиками  для  обозначения усовершенствований
         или коррекции ошибок для их менеджеров расширенной памяти. Следо-
         вательно,  Вашей  программе для проверки версии не следует связы-
         ваться с обеими цифрами.  Намного лучшая стратегия заключается  в
         выполнении сравнения на "больше или равно".
              В случае MS-DOS 4.0 пользователь мог бы устанавливать  драй-
         веры  третьей  стороны  для  компенсации недостатков в драйверах,
         обеспечиваемых  более ранними выпусками операционной системы, по-
         этому недостаточно  выполнить  проверку на MS-DOS 4.0 и полагать,
         что драйверы  спецификации  расширенной  памяти  присутствуют.  В
         действительности, драйверы MS-DOS 4.0 могут быть установлены, но,
         однако,  не управлять оборудованием.  В качестве простого  теста,
         вызовите функцию 1, "Получить состояние", поместив значение 40h в
         регистр AH и вызвав прерывание 67h; если аппаратура и программное
         обеспечение работают совместно, функция возвратит значение нуль в
         AH;  если нет,  Вы получите ненулевое значение.  Любое  ненулевое
         значение указывает на отказ, но значения 80h или 81h указывают на
         отказ оборудования, типичный при несоответствии драйверов. В этом
         случае  отобразите пользователю сообщение о том,  что драйверы не
         могут работать правильно. Если Ваше приложение тестирует работос-
         пособность для спецификации расширенной памяти MS-DOS 4.0, помни-
         те,  что ранние версии MS-DOS 4.0 не  поддерживают  функцию  19h,
         "Получить/установить   атрибут обработчика",  спецификации расши-
         ренной памяти LIM.

                 Определение доступного объема расширенной памяти

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

                           Размещение расширенной памяти

              В зависимости от динамического поведения Вашей  программы Вы
         можете  выбирать,  разместить ли всю расширенную память,  которая
         будет необходима единовременно,  или делать отдельные запросы  на
         размещение, когда требования времени выполнения программы меняют-
         ся.  Первый вариант более всего подходит, если количество страниц
         в  течение  времени выполнения варьируется незначительно.  Второй
         вариант более уместен, если ожидается, что требования расширенной
         памяти  программы  будут расти и убывать существенно во время вы-
         полнения  программы.  Этот вариант также более целесообразен, по-
         скольку остается  больше  расширенной  памяти,  доступной  другим
         программам, которые могут выполняться параллельно. Например, одна
         популярная программа электронных таблиц восстанавливает доступный
         объем расширенной памяти и размещает 1/8 этого объема каждый раз,
         когда требуется дополнительная память. Конечно, существует другой
         популярный пакет электронных таблиц,  который размещает целый не-
         размещенный пул расширенной памяти,  когда начинает  выполняться,
         не оставляя ничего другим программам,  которые впоследствии могут
         вызываться самой этой программой.
              При спецификации расширенной памяти LIM  3.2  обеспечивалась
         только  одна  функция размещения расширенной памяти:  "Разместить
         страницы", функция 4 спецификации расширенной памяти. Каждый зап-
         рос на размещение возвращает отдельный обработчик, который следу-
         ет использовать для ссылки на страницы, относящиеся к этому конк-
         ретному  размещению.   Количество   страниц,   связанное  с  этим
         обработчиком,  фиксировано с того момента,  когда  страницы  были
         размещены,  до того, когда они возвращаются менеджеру расширенной
         памяти.  Это ограничение препятствует динамическому размещению  и
         освобождению  расширенной памяти,  так как невозможно вернуть ме-
         неджеру расширенной памяти только некоторые из страниц, принадле-
         жащие конкретному обработчику.
              Данное ограничение было снято в спецификации расширенной па-
         мяти LIM 4.0 с помощью функции "Переразместить страницы", функция
         18 спецификации расширенной памяти. Она позволяет увеличивать или
         уменьшать количество страниц,  связанных с обработчиком,  в любой
         момент после размещения.  Новая функция не снимает всех  проблем,
         связанных  с динамическим управлением структурами данных в памяти
         спецификации расширенной памяти,  поскольку  логические  страницы
         могут добавляться или исключаться только с конца.  Например, если
         бы Вы хотели освободить третью логическую  страницу  обработчика,
         для  которого были размещены шесть логических страниц,  Вам приш-
         лось бы освобождать также логические страницы с четвертой по шес-
         тую. Данная функция также позволяет возвратить все страницы, свя-
         занные с обработчиком,  менеджеру расширенной памяти без возврата
         самого обработчика путем указания нового счетчика страниц, равно-
         го нулю.
              Для того,  чтобы завершить обсуждение размещения расширенной
         памяти,  рассмотрим идею исходных страниц, введенную в специфика-
         ции  расширенной  памяти LIM 4.0.  Исходные страницы - логические
         страницы,  которые некоторым подмножеством  стандартного  размера
         страниц  спецификации  расширенной памяти - 16Кбайт - были ведены
         для обеспечения дополнительной гибкости оборудования и  программ-
         ного  обеспечения  расширенной  памяти,  способного  поддерживать
         меньшие размеры страниц.  Например,  менеджер расширенной памяти,
         базирующийся на аппаратуре Intel 80386,  мог бы поддерживать раз-
         мер исходных страниц, равный 4Кбайт, обеспечивая надлежащим обра-
         зом  написанное  программное  обеспечение  способностью управлять
         расширенной памятью более  эффективно,  чем было бы возможно  при
         размере страниц в 16Кбайт.  Менеджеры расширенной памяти специфи-
         кации расширенной памяти 4.0, написанные для поддержки существую-
         щих  плат спецификации расширенной памяти,  поддерживают идею ис-
         ходных страниц тривиальным образом  -  размер  исходной  страницы
         идентичен размеру стандартной страницы.
              Исходные страницы размещаются с помощью функции 27, подфунк-
         ции 1,  "Разместить исходные страницы",  спецификации расширенной
         памяти.  У функции 27 спецификации расширенной памяти также  есть
         подфункция 0,  "Разместить стандартные страницы", которая обеспе-
         чивает идентичную услугу для функции "Разместить страницы" специ-
         фикации расширенной памяти LIM 3.2 с одним расширением:  она поз-
         воляет разместить нуль страниц для обработчика. Данное расширение
         также имеет место для подфункции 1.

                           Адресация расширенной памяти

              Адреса обычной  памяти  на процессорах Intel 80х86 (в режиме
         реальных адресов) определяются парой 16-битовых компонент: значе-

                                      - 7-34 -
         нием сегмента,  иногда именуемым селектором, и значением смещения
         в байтах.
              Формирование адреса  элемента  данных  в  расширенной памяти
         несколько более сложное.  Для начала, элементы данных в расширен-
         ной памяти обладают логическим адресом из трех частей:  номер об-
         работчика,  номер логической страницы и байтовым смещением в пре-
         делах  логической  страницы.  Кроме  того,  в отличие от элемента
         данных в обычной памяти, положение которого остается постоянным с
         момента размещения,  физический адрес элемента данных в расширен-
         ной памяти может меняться, так как он зависит от текущего состоя-
         ния отображения системы расширенной памяти.
              Эти характеристики делают задачу сохранения следа  данных  в
         расширенной памяти двусторонней проблемой: управления логическими
         адресами и управления физическими адресами.

                          Управление логическими адресами

              Спецификация расширенной памяти LIM определяет обработчик как
         16-битовую величину (даже  когда поддерживается максимум 255 обра-
         ботчиков)  и номер логической страницы как 14-битовое число (логи-
         ческие страницы нумеруются от 0 до 2047).  Субадресация  объектов
         внутри  логической страницы не имеет отношения к менеджеру расши-
         ренной памяти,  но является главной заботой разработчика приложе-
         ния расширенной памяти. В особенности, данные какого типа следует
         хранить в расширенной памяти и как ими следует управлять?
              Общий метод  сохранения  следа элемента данных в расширенной
         памяти без обращения к побитным трюкам требует от Вашей программы
         обеспечивать три  16-битовые  переменные на элемент - обработчик,
         номер страниц и байтовое смещение внутри страницы.  Эти накладные
         расходы  делают  применение расширенной памяти наиболее пригодным
         для запоминания структур данных,  которые относительно велики  по
         сравнению с указателями,  применяемыми для обращения к ним.  Мало
         смысла сохранять 4-байтовый элемент данных в  расширенной памяти,
         если  при этом требуется 6-байтовый указатель для доступа к нему.
              Еще одним фактором для  рассмотрения  является  динамическое
         поведение  структур  данных,  которые  Вы намереваетесь хранить в
         расширенной памяти.  Поддержание связного списка,  состоящего  из
         элементов переменной длины могло бы быть интересным предложением,
         если он размещается в расширенной памяти.  Довольно сложная схема
         управления  памятью  потребовалась бы для того,  чтобы эффективно
         уплотнять свободное  пространство  и  обрабатывать  переполнение,
         когда размер списка превысит размер логической страницы.
              В действительности, эффективные способы управления расширен-
         ной  памятью  совершенно аналогичны методам поддержания файлового
         буфера и индексации, применяемым системами управления базами дан-
         ных.  Существующая проблема является проблемой управления элемен-
         тами данных переменного размера (записи,  массивы и т.д.) в огра-
         ниченном  множестве  буферов  фиксированного  размера (16-Кбайт в
         случае спецификации расширенной памяти). Неудивительно, что неко-
         торые программы баз данных для персональных компьютеров,  совмес-
         тимых с IBM PC,  пользуются преимуществами данного сходства путем
         хранения  индексов файлов или,  даже целых файлов,  в расширенной
         памяти, когда она имеется в наличии.
              Потребовались бы  значительные усилия для использования рас-
         ширенной памяти в качестве  средства  управления  памятью  общего
         назначения в Ваших программах.  Однако,  Вы можете эффективно ис-
         пользовать расширенную память во многих обычных задачах  сохране-

                                      - 7-35 -
         ния данных при простых специальных подходах.  Примеры приложений,
         перечисленные в конце данной главы показывают, какие относительно
         несложные  методы  управления  расширенной памятью достаточны для
         обработки задач управления динамической памятью. Последующие раз-
         делы  в данной главе посвящены детальному рассмотрению нескольких
         функций,  добавленных в спецификации расширенной памяти LIM  4.0,
         которые значительно сокращают усилия по программированию, необхо-
         димые для работы с кодами и данными в расширенной памяти.

                          Управление физическими адресами

              В спецификации  расширенной  памяти LIM 3.2 физический адрес
         элемента данных в логической странице,  которая в  данный  момент
         отображается  в  физическую страницу,  вычисляется по отношению к
         базе 64-Кбайтного кадра страниц.  Адрес сегмента для этого  кадра
         страниц получается посредством функции 2  "Получить адрес сегмен-
         та кадра страниц",  спецификации расширенной памяти. Деление раз-
         мера кадра страниц на размер стандартной логической страницы дает
         четыре физических страницы,  пронумерованных от 0 до  3,  которые
         могут  указываться в вызовах функции спецификации расширенной па-
         мяти. Все четыре страницы являются смежными в памяти - адрес каж-
         дой следующей на 16Кбайт выше, чем адрес предшествующей.
              Спецификация расширенной памяти LIM 4.0,  включающая в  себя
         идею многих, возможно несмежных, кадров страниц усовершенствован-
         ной спецификации расширенной памяти AQA,  делает вычисление физи-
         ческого  адреса или более простым или более сложным в зависимости
         от Вашей точки зрения.  В дополнение к функции 2 спецификации LIM
         4.0  менеджер  расширенной  памяти  может  теперь обеспечить Вашу
         программу таблицей номеров физических страниц и  адресов  сегмен-
         тов, соответствующих каждой физической странице.
              Функция 25, подфункция 1, "Получить отсчет элементов адресов
         физической страницы",  спецификации расширенной памяти возвращает
         количество отображаемых физических страниц, поддерживаемых менед-
         жером расширенной памяти.  Используйте эту подфункцию для опреде-
         ления размера массива, адрес которого передается функции 25, под-
         функции 0,  "Получить массив  отображаемых  физических  адресов",
         спецификации  расширенной памяти,  которая заполнит данный массив
         адресами сегментов в нарастающем порядке и номерами соответствую-
         щих страниц.
              В то  время как в спецификации расширенной памяти LIM 4.0 не
         требуется от менеджера расширенной памяти обеспечивать более  че-
         тырех стандартных физических страниц, определенных в спецификации
         3.2,  менеджер  расширенной  памяти  может  обеспечивать  до   36
         16-Кбайтных физических страниц. До 12 страниц может размещаться в
         пространстве между 768К и 960К (шестнадцатиричные адреса от С0000
         до  F0000)  и  до  24 страниц может размещаться между 256К и 640К
         (шестнадцатиричные адреса от 40000 до A0000).
              Доступ к кадру страниц над 640К возможен для любого приложе-
         ния спецификации расширенной памяти. Доступ к отображаемой памяти
         ниже 640К,  на которую спецификация ссылается как на отображаемую
         обычную память,  предназначен для разработчиков расширений опера-
         ционной системы, таких как Windows 2.0 фирмы Microsoft.
              Количество физических страниц в кадре страниц над 640К может
         изменяться  в  зависимости от раскладки пространства ПЗУ на конк-
         ретной машине.  Видеоадаптеры различных типов могут занимать  су-
         щественную  долю  пространства  ПЗУ для своих дисплейных буферов.
         Многие типы адаптеров ввода-вывода,  включая сетевые платы и дис-

                                      - 7-36 -
         ковые контроллеры,  содержат расширения ПЗУ BIOS, которые появля-
         ются в адресном пространстве между C0000 и F0000.
              Программные эмуляторы  должны  обычно размещать кадр страниц
         спецификации расширенной памяти вне адресного  пространства  DOS,
         ниже  640К,  потому что у большинства компьютеров,  совместимых с
         IBM PC, нет ОЗУ между 640К и 960К. Некоторые программные эмулято-
         ры спецификации расширенной памяти могут пользоваться преимущест-
         вом возможности некоторых плат расширенной памяти  отображать 64К
         или более ОЗУ в пространство над 640К.
              Опасно делать какие-либо упрощающие предположения о  положе-
         нии и выравнивании кадра страниц. Некоторые существующие приложе-
         ния спецификации расширенной памяти делают такие допущения, пола-
         гаясь   на  выравнивание  кадра  страниц,  обычно  обеспечиваемое
         оборудованием спецификации расширенной  памяти,  для  того  чтобы
         сохранить  пространство  памяти для указателей расширенной памяти
         или для упрощения вычисления адресов в расширенной памяти.  Одна-
         ко,  кадр страниц, обеспечиваемый чисто программными реализациями
         менеджера расширенной памяти, может не обладать таким же выравни-
         ванием,  как  кадр страниц платы спецификации расширенной памяти.
         Для того чтобы позволить Вашим приложениям использоваться в  сис-
         темах  с чисто программными менеджерами расширенной памяти, здесь
         приводятся некоторые упрощающие допущения,  которые  Вам  следует
         избегать при программировании:
              1. Поскольку  аппаратура  спецификации  расширенной   памяти
         обеспечивает кадры страниц,  выровненные на 16-Кбайтных границах,
         только старший байт адреса сегмента имеет значение при формирова-
         нии физического адреса. В результате некоторые программы не запо-
         минают младший байт сегмента кадра страниц, полагая, что он будет
         равен нулю. В спецификации расширенной памяти LIM не указывается,
         что физические страницы должны выравниваться  по  любой  границе,
         более высокой, чем граница параграфа.
              2. Некоторые приложения спецификации расширенной памяти  вы-
         полняют вычисление адреса расширенной памяти,  полагая,  что кадр
         страниц находится над 640К или,  что его адрес в  памяти  больше,
         чем адрес самого приложения.  Кадр страниц,  обеспечиваемый прог-
         раммным менеджером расширенной памяти может не  подчиняться этому
         допущению.

                        Чтение и запись расширенной памяти

              В то  время как спецификация расширенной памяти LIM 4.0 поз-
         воляет менеджеру расширенной памяти управлять до 32 Мбайтами дан-
         ных, объем, доступный Вашей программе в каждый данный момент вре-
         мени ограничен числом физических страниц,  присутствующих в кадре
         страниц.
              Перед чтением или записью данных из расширенной памяти долж-
         на  быть сделана доступной для программы логическая страница,  на
         которой эти данные размещены,  путем отображения логической стра-
         ницы в физическую страницу в кадре страниц. Отображение страниц -
         в действительности  сердцевина  управления  памятью  спецификации
         расширенной памяти; для большинства приложений, которые пользуют-
         ся расширенной памятью, это - наиболее часто используемая функция
         менеджера расширенной памяти.
              В спецификации расширенной памяти LIM 3.2  для  данной  цели
         обеспечена  функция  5, "Отобразить/перестать отображать страницы
         обработчика". Путем поддержания  обработчика,  номера  логической
         страницы  и  номера  физической страницы одна логическая страница

                                      - 7-37 -
         отображается в одну физическую. Указание -1 или 0FFFFH в качестве
         номера логической страницы делает любую логическую страницу,  ко-
         торая отображается в указанную физическую, недоступной программе.
         Конечно,  содержимое страницы, которая перестала отображаться та-
         ким образом,  не  изменяется и может снова сделать доступным пос-
         ледующие отображением этой логической страницы в физическую.
              Функция 17, "Отобразить/перестать отображать страницы многих
         обработчиков",  спецификации  расширенной памяти LIM 4.0 добавила
         более краткие и гибкие средства отображения страниц.  В одном об-
         ращении  данная функция может отобразить или перестать отображать
         логические страницы в такое число страниц,  которое  поддерживает
         менеджер расширенной памяти.  Программы, которые часто отображают
         много страниц за раз, могут достигать явно более высокой произво-
         дительности  из-за  сокращения  фиксированных накладных расходов,
         связанных с каждым вызовом менеджера расширенной памяти.
              Как и в случае функции 5, указание номера логической страни-
         цы,  равного -1 (0FFFFH) заставляет  любую  логическую  страницу,
         отображенную  в указанную физическую страницу, перестать отобра-
         жаться.

                      Два способа задания физических страниц

              Начальное рассмотрение физических страниц в связи со  специ-
         фикацией  расширенной  памяти LIM 3.2 установило,  что физические
         страницы определяются порядковым номером (от 0 до 3 в  специфика-
         ции расширенной памяти LIM 3.2). В спецификации расширенной памя-
         ти LIM 4.0 обеспечивается дополнительный способ задания  физичес-
         ких  страниц:  фактическим  адресом  сегмента  начала  физической
         страницы.  Например, если адрес кадра страниц, возвращенный функ-
         цией "Получить адрес кадра страниц",  был равен CC00h, третья фи-
         зическая страница в пределах кадра страниц могла  бы определяться
         своим порядковым номером,  2, или адресом сегмента, D400h. Данный
         адрес сегмента был вычислен путем прибавления трижды размера  фи-
         зической страницы (в параграфах) к базовому адресу кадра страниц.
              Любые функции спецификации расширенной памяти LIM 4.0, беру-
         щие номера физических страниц в  качестве  параметров,  позволяют
         указание  физических  страниц  порядковыми  номерами или адресами
         сегментов. Вы можете выбирать наиболее удобный для Вашей програм-
         мы  метод путем задания кода подфункции в регистре AL для функций
         спецификации расширенной памяти 4.0, которые принимают номера фи-
         зических страниц. Код подфункции 00h показывает, что значения фи-
         зических страниц определены порядковыми номерами физических стра-
         ниц,  в то время как код подфункции 01h показывает,  что значения
         физических страниц заданы соответствующими адресами сегментов.
              Как было  описано  в  предшествующем  разделе,  перекрестная
         ссылка между номерами физических страниц и их  адресами сегментов
         получается  от  менеджера  расширенной памяти посредством функции
         25, "Получить массив физических адресов".
              Когда логическая  страница  отображена  в  физическую,  Ваша
         программа может затем адресовать любые  данные  в  этой  странице
         удаленным указателем.Языковые процессоры,которые генерируют толь-
         ко так называемые программы малых моделей,  могут не поддерживать
         использование 32-битовых (удаленных)   указателей  для  именуемых
         элементов данных. В отсутствие такой поддержки некоторые компиля-
         торы  обеспечивают библиотечную программу,  которая копирует блок
         данных из произвольного сегмента  и  адреса  смещения  в  область
         внутри  единственного 64-Кбайтного сегмента данных программы.  Не

                                      - 7-38 -
         имея даже этого,  Вы можете написать  интерфейсную  программу  на
         языке ассемблера для получения того же результата.
              На рис. 7-3 показана гипотетическая конфигурация расширенной
         памяти 384К,  которая используется двумя программами, электронной
         таблицей и программой буферизации принтера.  Иллюстрируются неко-
         торые  динамические  отношения между программами,  обработчиками,
         логическими страницами и физическими страницами, а именно:
              * Две (или более) независимых программы  могут  пользоваться
         расширенной памятью одновременно без взаимного влияния.
              * У одной программы может быть более одного обработчика спе-
         цификации расширенной памяти, размещенного для нее, - фоновая за-
         дача в примере обладает двумя обработчиками.
              * Последовательно  пронумерованные  логические  страницы  не
         приходится отображать в последовательные  физические  страницы  -
         активная приоритетная программа имеет логические страницы 6, 7, 2
         и 1, отображенные в физические страницы 0-3.

              Функция "Передвинуть/обменять  область памяти" (24), которая
         была добавлена как часть спецификации расширенной памяти  LIM 4.0
         обеспечивает  исчерпывающие средства для управления передвижением
         областей данных,  длиной до 1Мбайта,  между расширенной памятью и
         обычной  памятью.  Данная функция также позволяет передвигать или
         обменивать данные, когда обе указанные области находятся в преде-
         лах расширенной памяти или, когда обе области находятся в обычной
         памяти.
              Подфункция передвижения  (00h)  копирует  содержимое  облас-
         ти-источника в область-приемник.  Если указанные области перекры-
         ваются,  менеджер расширенной памяти выбирает  такое  направление
         передвижения,  чтобы область-приемник получала неповрежденную ко-
         пию области источника. Когда часть области-источника перекрывает-
         ся  целевой  областью  в течение операции передвижения,  менеджер
         расширенной памяти возвратит код состояния,  указывающий  на  это
         (как всегда в регистре AH).
              Подфункция обмена (01h) обменивает местами две области памя-
         ти: любая или обе области могут быть в расширенной памяти или
              В отличие от подфункции передвижения  подфункция  обмена  не
         разрешает задания перекрывающихся областей.
              Удобной чертой обеих подфункций является  то,  что  операции
         передвижения или обмена на меняют контекста текущего отображения.
         Любые логические страницы,  которые Ваша программа могла  отобра-
         зить в кадр страниц, не будут изменяться функцией 25, так что для
         Вашей программы нет необходимости сохранять  контекст отображения
         перед применением данной функции.
              Функция "Передвинуть/обменять  область   памяти"   избавляет
         программиста  от нескольких утомительных программных работ,  свя-
         занных с управлением расширенной памятью,  которые были жизненным
         фактом  в  более  ранних  версиях спецификации расширенной памяти
         LIM. Тем не менее, важно контролировать код состояния, возвращае-
         мый данной функцией.  Существуют 13 различных ошибок, которые мо-
         гут случиться в течение операции передвижения или обмена.

                  Разделение расширенной памяти между программами

              При спецификации расширенной памяти LIM 3.2  ничто  не  пре-
         дотвращало две программы от разделения данных в расширенной памя-
         ти.  Программе требовалось только знать обработчик и номер  логи-
         ческой   страницы   данных,  к  которым  она  хотела  обратиться.

                                      - 7-39 -
         Поскольку программа не  может  знать  apriori,  какой  обработчик
                                                  384Kb расширенной
                                         B   C           памяти
         ЪДДДДДДДДДД¬                   ЪДДВДД¬   ЪДДДДДДДДДДДДДДДД¬ Д¬
         ¦          ¦ДДДДДДДДДДДДДДВДДДД¦ 1¦ 0¦   ГДДДДДДДДДДДДДДДДґ
         ¦    A     ¦              ¦    АДДґ 1¦   ГДДДДДДДДДДДДДДДДґ  24
         ¦          ¦              ¦       ¦ 2¦   ГДДДДДДДДДДДДДДДДґ
         АДДДДДДДДДДЩ              ¦       ¦ 3¦   ГДДДДДДДДДДДДДДДДґ   С
                                   ¦       ¦ 4¦   ГДДДДДДДДДДДДДДДДґ
     Начальный                     ¦       ¦ 5¦   ГДДДДДДДДДДДДДДДДґ   Т
     сегмент                       ¦       ¦ 6¦   ГДДДДДДДДДДДДДДДДґ
     физической                    ¦       ¦ 7¦   ГДДДДДДДДДДДДДДДДґ   Р
     страницы    D   B  C          ¦    ЪДД†ДДґ   ГДДДДДДДДДДДДДДДДґ
         CC00H  ЪДДВДДВДД¬         ¦  ЪД¦ 2¦ 0¦   ГДДДДДДДДДДДДДДДДґ   А
                ¦ 0¦ 2¦ 6¦ДДДДД¬   ¦  ¦ АДДґ 1¦Д¬ ГДДДДДДДДДДДДДДДДґ
         D000H  ГДД†ДД†ДДґ     ¦   ¦  ¦  ЪД¦ 2¦ ¦ ГДДДДДДДДДДДДДДДДґ   Н
                ¦ 1¦ 2¦ 7¦ДДД¬ ¦   ¦  ¦  ¦ ¦ 3¦ ¦ ГДДДДДДДДДДДДДДДДґ
         D400H  ГДД†ДД†ДДґ   ¦ ¦   ¦  ¦  ¦ ¦ 4¦ ¦ ГДДДДДДДДДДДДДДДДґ   И
                ¦ 2¦ 2¦ 2¦ДДД¦Д¦ДДД¦ДД¦ДДЩ ¦ 5¦ ¦ ГДДДДДДДДДДДДДДДДґ
         D800H  ГДД†ДД†ДДґ   ¦ АДДД¦ДД¦ДДДД¦ 6¦ ¦ ГДДДДДДДДДДДДДДДДґ   Ц
                ¦ 3¦ 2¦ 1¦Д¬ АДДДДД¦ДД¦ДДДД¦ 7¦ ¦ ГДДДДДДДДДДДДДДДДґ
                АДДБДДБДДЩ ¦       ¦  ¦    ¦ 8¦ ¦ ГДДДДДДДДДДДДДДДДґ   Ы
              Кадр страниц ¦       ¦  ¦    ¦ 9¦ ¦ ГДДДДДДДДДДДДДДДДґ
                           ¦       ¦  ¦ ЪДД†ДДґ ¦ ГДДДДДДДДДДДДДДДДґ
                           ¦       АДДДД¦ 3¦ 0¦ ¦ ГДДДДДДДДДДДДДДДДґ   П
                           ¦          ¦ АДДґ 1¦ ¦ ГДДДДДДДДДДДДДДДДґ
         ЪДДДДДДДДДД¬      ¦          ¦    ¦ 2¦ ¦ ГДДДДДДДДДДДДДДДДґ   О
         ¦          ¦      ¦          ¦    ¦ 3¦ ¦ ГДДДДДДДДДДДДДДДДґ
         ¦    E     ¦ДДДДДДДДДДДДДДДДДЩ    АДДЩ ¦ ГДДДДДДДДДДДДДДДДґ 16Kb
         ¦          ¦      ¦                    ¦ ГДДДДДДДДДДДДДДДДґ
         АДДДДДДДДДДЩ      АДДДДДДДДДДДДДДДДДДДДЩ АДДДДДДДДДДДДДДДДЩ ДЩ

          Рис.7-3. Моментальный снимок расширенной памяти с приоритетной
        и фоновой задачами. В настоящий момент активна приоритетная задача

         A - программа буферизации принтера (фоновая задача),  владеет об-
             работчиками 1 и 3;
         B - обработчик;
         C - номер логической страницы;
         D - номер физической страницы;
         E - электронная таблица (приоритетная задача),  владеет
             обработчиком 2 в обычной памяти.

         (ки)   будет размещен  для нее менеджером расширенной памяти, две
         программы,  разработанные так, чтобы разделять данные в расширен-
         ной  памяти,  должны  подготовить некоторые средства для передачи
         номеров обработчиков во время выполнения.
              В спецификации  расширенной памяти LIM 4.0 разделение данных
         в расширенной памяти сделано немного более легким путем обеспече-
         ния  возможности  связывания  с обработчиком 8-символьного имени.
         Для поддержки данной способности были введены две функции  менед-
         жера  расширенной  памяти:  "Получить/установить имя обработчика"
         (функция 20) и "Получить каталог обработчика" (функция 21).
              Подфункция 00h функции 20, "Получить имя обработчика", возв-
         ращает 8-символьное имя,  связанное  с  обработчиком,  переданным
         функции.  Подфункция 01h, "Установить имя обработчика", связывает
         8-символьную  строку с указанным номером обработчика.  Не сущест-

                                      - 7-40 -
         вует ограничений на символы, используемые для формирования имени,
         и все 8 символов являются  значащими  (имя  не  является  строкой
         ASCII,  завершающейся  байтом  "Пусто" - нулями).  Обработчик без
         имени приобретет имя,  состоящее из 8 байтов двоичных нулей  (или
         "Пусто"  ASCII,  если Вам это предпочтительнее).  Имя обработчика
         устанавливается в "Пусто" при инициализации менеджера расширенной
         памяти, когда обработчик размещается и  когда обработчик освобож-
         дается. Вы можете изменить имя обработчика в любое время, включая
         сброс его в нули.  Единственное ограничение состоит в том, что не
         разрешается двум обработчикам иметь одинаковые имена.
              Менеджер расширенной памяти обеспечивает функцию 21,  "Полу-
         чить каталог обработчика", для определения того, какой обработчик
         связан с конкретным именем или для обеспечения таблицы имен обра-
         ботчиков,  связанных  с  каждым активным обработчиком. Подфункция
         00h, "Получить каталог обработчика", возвращает эту таблицу в об-
         ласть данных, предоставленную пользователем. Поскольку специфика-
         ция поддерживает до 255 обработчиков,  8-байтовое имя обработчика
         плюс 2-байтовое значение обработчика,  целая таблица может потре-
         бовать до 2550 байтов.  Фактическое число обработчиков, поддержи-
         ваемых менеджером расширенной памяти,  может быть получено с  по-
         мощью  подфункции 02h,  "Получить общее количество обработчиков".
         Умножив это число на 10, получаем размер области, необходимой для
         сохранения каталога обработчиков.  Подфункция 01h, "Искать имено-
         ванный обработчик", обеспечивает для программы поиск обработчика,
         связанного с данным именем, без необходимости просмотра всего ка-
         талога обработчиков или запроса имени,  связанного с каждым номе-
         ром обработчика.

                       Выполнение кода в расширенной памяти

              Всегда было возможно использовать память спецификации расши-
         ренной памяти LIM для хранения и выполнения исполняемого кода, но
         не  всегда  это  было  просто.  Во-первых,  максимальный размер в
         64 Кбайт  кадра  страниц  спецификаций  до 4.0 ограничивал размер
         оверлея,  который мог бы быть активным в данный  момент  времени.
         Также каждому разработчику приходилось разрабатывать полный меха-
         низм связывания, который позволял коду в обычной памяти выполнять
         код, находящийся в расширенной памяти.
              У спецификации расширенной памяти LIM 4.0 есть потенциал для
         смягчения некоторых этих  проблем.  Теперь  могут  поддерживаться
         кадры  страниц,  большие 64К,  хотя менеджеры расширенной памяти,
         написанные для плат,  разработанных для спецификации 3.2, вероят-
         но,  не  смогут  обеспечивать большие размеры кадра страниц.  Для
         способствования отображению и связыванию кодовых объектов в  рас-
         ширенной памяти были введены две новые функции, "Изменить отобра-
         жение страниц и перейти" (22) и "Изменить отображение  страниц  и
         вызвать".
              "Изменить отображение страниц и перейти" отображает нуль или
         более  логических страниц (до максимального количества физических
         страниц,  поддерживаемых менеджером расширенной  памяти)  в  кадр
         страниц и передает управление указанному целевому адресу. В отли-
         чие от любой другой функции менеджера расширенной  памяти  данная
         функция  не возвращает управление команде,  следующей за командой
         "int 67h" (кроме случаев, когда менеджер расширенной памяти обна-
         руживает  ошибку  перед переходом к целевому адресу).  Программа,
         которая получает управление в результате выполнения данной  функ-
         ции  отвечает  за установление собственного связывания по выходе.

                                      - 7-41 -
         Когда целевой адрес  получает  управление,  содержимое  регистров
         процессора и флаги являются такими,  какими они были,  когда было
         выдано прерывание менеджера расширенной  памяти.  Таким  образом,
         программы  могут передавать параметры целевой программе в регист-
         рах.  Контекст отображения,  существовавший перед вызовом  данной
         функции не сохраняется.
              Функция "Сменить отображение  страниц  и  вызвать"  является
         аналогом  удаленной команды CALL 80х86.  Аналогично функции "Сме-
         нить отображение страниц и  перейти"  данная  функция  отображает
         нуль  или  более  логических страниц (до максимального количества
         физических страниц,  поддерживаемого менеджером расширенной памя-
         ти) в кадр страниц и передает управление целевому адресу. Страни-
         цы,  отображенные перед тем,  как происходит передача  управления
         называются новым отображением страниц. В отличие от функции "Сме-
         нить отображение страниц и перейти" целевая  программа возвращает
         управление менеджеру расширенной памяти (и по существу,  програм-
         ме, которая выдала "Сменить отображение страниц и вызвать") путем
         выполнения  команды  удаленного  возврата RETURN.  Когда менеджер
         расширенной памяти вновь получает управление от целевой  програм-
         мы, множество  страниц,  называемое  старым  отображением страниц,
         отображается в кадр страниц и менеджер расширенной памяти  возвра-
         щает управление исходной вызывавшей программе.  Содержимое и ново-
         го   и старого отображения страниц задается вызывавшей программой.
         Регистры вызывавшей программы сохраняются в течение процесса.  Со-
         держимое регистров при входе в целевую программу является тем  же,
         какое было во время выдачи прерывания менеджера расширенной памяти
         вызывавшей программой.
              Данная функция  способна  поддерживать  вложенные  вызовы  -
         программа, в которую вошли посредством "Сменить отображение стра-
         ниц и вызвать", может сама использовать эту функцию. Для сохране-
         ния контекста на каждом уровне вызова менеджер расширенной памяти
         использует  стек  вызывающей программы.  Количество байтов стека,
         необходимое для менеджера расширенной памяти для выполнения  это-
         го,  получается путем применения подфункции 02,  "Получить размер
         пространства стека отображения страниц", функции "Сменить отобра-
         жение страниц и вызвать".

                          Освобождение расширенной памяти

              Надлежащим образом  сконструированные программы перед завер-
         шением закрывают файлы и освобождают обычную память, которая была
         размещена из DOS.  Аналогичным образом, ресурсы расширенной памя-
         ти,  размещенные Вашей программой,  должны возвращаться менеджеру
         расширенной памяти перед завершением программы.
              Поскольку он работает независимо от операционной  системы, у
         менеджера расширенной памяти нет способа определения,  когда Ваша
         программа завершилась.  Если Ваша программа не  освобождает  явно
         все страницы расширенной памяти,  которые она размещала перед вы-
         ходом,  следующая программа, которая попытается пользоваться рас-
         ширенной памятью,  может найти, что расширенная память заполнена,
         даже хотя данные в расширенной памяти более не используются.
              Если Вы  намереваетесь писать здравые приложения с использо-
         ванием расширенной памяти, для Вашей программы будет недостаточно
         возвращать ресурсы менеджеру расширенной памяти перед нормальными
         завершениями.  Более совершенное обращение должно включать в себя
         код  для очистки ресурсов расширенной памяти в драйвере прекраще-
         ния программы (Break = Control-C),  драйвере критической ошибки и

                                      - 7-42 -
         драйвере  деления  на  нуль.  Прежде всего обработка этих условий
         требует значительного объема программирования на языке  ассембле-
         ра,вместе со способностью разобрать" Техническое справочное руко-
         водство по DOS (DOS Technical Reference Manual)". Правда, недавно
         в некоторые продукты языков высокого уровня,  включая С 5.0 фирмы
         Microsoft и Турбо-Паскаль 4.0 и Турбо-C фирмы Borland,  были вве-
         дены  средства для обработки этих условий в самих языках высокого
         уровня.  У программистов, пользующимся этими продуктами для напи-
         сания  приложений спецификации расширенной памяти,  более нет ра-
         зумных оснований истолковывать процедуры ненормальных завершений.

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

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

                  Сравнение нерезидентных и резидентных программ

              Резидентность или нерезидентность в памяти в действительнос-
         ти  не является важным вопросом при использовании расширенной па-
         мяти. Существенная разница состоит в том, что нерезидентные прог-
         раммы   выполняются   синхронно,   то   есть   явно   по  запросу
         пользователя.  DOS управляет переходом между программами, так что
         состояние машины и операционная система - управляемые ресурсы яв-
         ляются управляемыми надлежащим образом.
              Программы обслуживания  прерываний  (включая  встроенные   в
         драйверы устройства и в программы,  остающиеся в памяти по завер-
         шении) получают управление асинхронно, откликаясь на аппаратурные
         события.  DOS не играет никакой роли в переходе между выполняемой
         в данный момент программой и программой  обслуживания прерывания.
         Таким образом,  отдельная программа обслуживания прерывания отве-
         чает за сохранение состояния процессора перед его изменением и за
         восстановление  исходного  состояния  перед  возвратом управления
         прерванной программе.  У менеджера расширенной памяти также  есть
         состояние,  на которое часто ссылаются как на контекст. Программы
         обслуживания прерываний,  использующие расширенную память, должны
         сохранять  этот  контекст перед тем,  как изменить его,  и должны
         восстанавливать его перед выходом.
              Заметная доля набора функций спецификации расширенной памяти
         4.0 посвящена требованиям резидентных программ, операционных сис-
         тем и рабочих сред. Кроме того, в функции, требующиеся для сохра-
         нения  и  восстановления  контекста  менеджера расширенной памяти

                                      - 7-43 -
         спецификации расширенной памяти 4.0, также были введены несколько
         функций, специально разработанных для обеспечения совместной ком-
         поновки между операционными системами или  многозадачными средами
         (OS/E на языке спецификации расширенной памяти) и менеджером рас-
         ширенной памяти.  Это компоновка должна была бы  позволить  прог-
         раммному обеспечению операционных систем и многозадачных сред ис-
         пользовать  средства,  которые  могут  быть  включены   в   новые
         разработки аппаратуры спецификации расширенной памяти,  такие как
         механизмы быстрого переключения задач и неразрушаемая память.
              С нашим  пониманием идей расширенной памяти,  представленных
         до сих пор в данной главе,  мы теперь может  рассматривать  более
         сложные  средства  спецификации  расширенной памяти LIM,  которые
         поддерживают резидентные  программы  и  программное  обеспечение,
         ориентированное системно.

                 Обнаружение наличия менеджера расширенной памяти

              Драйверам устройств,  которые  загружаются до того,  как DOS
         полностью инициализирована,  не полагается выдавать вызовы файло-
         вой системы DOS.  Большинство вызовов DOS также являются неразре-
         шенными для резидентных программ,  которые не обрабатывают специ-
         альным  образом  проблему отсутствия повторного входа DOS.  Таким
         образом, метод открытого обработчика, представленный ранее в дан-
         ной  главе,  не  является подходящим средством для этих программ,
         чтобы выявлять наличие расширенной памяти.
              Альтернативный метод,  который может применяться любой прог-
         раммой - метод получения вектора прерывания. Данный метод работа-
         ет следующим образом:
              1. Выдается функция DOS "Получить вектор"  (прерывание  21h,
         функция  35h) для получения адреса программного прерывания менед-
         жера расширенной памяти (67h).
              2. Менеджер  расширенной  памяти  находится  внутри драйвера
         символьного устройства DOS, у которого заголовок устройства нахо-
         дится  по  нулевому смещению в сегменте,  возвращаемом в регистре
         ES,  предыдущим шагом. У всех драйверов символьных устройств есть
         8-символьное поле имени устройства, размещающееся по смещению 0Ah
         в заголовке устройства,  которое DOS использует для указания уст-
         ройства, когда вызовы файловой системы ссылаются на него. Сравни-
         те имя устройства по смещению 0Ah в сегменте,  возвращенном в ре-
         гистре  ES на шаге 1,  со строкой "EMMXXXX0." (Вспомните,  что на
         это имя устройства была ссылка на вызове открытия, использованном
         как  часть  метода открытого обработчика.) Если строки совпадают,
         менеджер расширенной памяти присутствует.

                               Управление контекстом

              Если Вы уже писали  программы  обслуживания  прерываний  для
         драйверов  устройств или программ,  остающихся после завершения в
         памяти,  Вы уже без сомнения знаете,  что такие программы  должны
         сохранять содержимое любого регистра процессора,  которое изменя-
         ется данной программой, на входе и должны восстанавливать эти ре-
         гистры перед выходом. Данная идея сохранения состояния процессора
         (регистры, указатель команд и слово флагов составляют его состоя-
         ние) должна также быть применимой для менеджера расширенной памя-
         ти.
              В сущности, состояние  менеджера расширенной памяти или кон-
         текст - содержимое регистров отображения (или их  имитации). Ваша

                                      - 7-44 -
         программа  должна сохранять контекст менеджера расширенной памяти
         перед вызовом любой функции,  которая может  его  изменить,  и  -
         восстанавливать исходный контекст перед передачей управления.
              В спецификации расширенной памяти LIM 3.2  предусмотрены два
         набора функций для данной цели. Наиболее проста для использования
         пара функций 8/9,  именуемых "Сохранить  отображение  страниц"  и
         "Восстановить  отображение страниц".  Первая   для данного номера
         обработчика   сохраняет текущий контекст во внутренней для менед-
         жера расширенной памяти области; внутри Вашей программы памяти не
         требуется.  Последняя функция  для данного такого же номера обра-
         ботчика   восстанавливает  контекст,  ранее запомненный для этого
         обработчика во внутренней области сохранения менеджера  расширен-
         ной памяти.  Хотя ими несложно пользоваться,  у этих функций есть
         некоторые ограничения,  которые ведут к рекомендации избегать  их
         применения в новых программах.
              Первое ограничение заключается в том,  что для каждого обра-
         ботчика обеспечивается максимальная область сохранения, а некото-
         рые менеджеры расширенной памяти не поддерживают область сохране-
         ния для всех возможных обработчиков. Результат состоит в том, что
         Ваша программа перестает быть полностью реентерабельной,  если  в
         ней для сохранения и восстановления контекста менеджера расширен-
         ной памяти используются эти функции,  поскольку каждое сохранение
         для данного обработчика должно сопровождаться восстановлением для
         этого же обработчика,  перед тем как  отображение  страниц  может
         быть  вновь  сохранено  с помощью того же обработчика.   Еще одно
         ограничение состоит в том, что эти функции сохраняют и восстанав-
         ливают контекст четырех физических страниц, определенных в специ-
         фикации расширенной памяти LIM 3.2.
              Для преодоления  этих ограничений в спецификации расширенной
         памяти LIM 3.2 имеется функция 15  "Получить/установить отображе-
         ние  страниц".  В отличие функций 8 и 9, которые сохраняют и вос-
         станавливают контекст из внутренней области менеджера расширенной
         памяти,  данная  функция  сохраняет и восстанавливает контекст из
         области,  обеспечиваемой вызывающей программой.  Подфункция   00h
         "Получить  отображение  страниц",  запоминает  контекст менеджера
         расширенной памяти в буфере пользователя,  указываемом регистрами
         ES:DI. Подфункция 01h  "Установить отображение страниц"  загружа-
         ет контекст менеджера расширенной памяти из  буфера пользователя,
         указываемом регистрами DS:SI. Подфункция 02h  "Получить и устано-
         вить отображение страниц"  делает то, что подразумевает ее наиме-
         нование, сохраняя контекст менеджера расширенной памяти в буфере,
         указываемом ES:DI, и загружая новый контекст из области, указыва-
         емой DS:SI. Не следует делать никаких допущений о размере буфера,
         необходимого для получения сохраненного контекста.  Получайте его
         от менеджера расширенной памяти с помощью  подфункции 03h  "Полу-
         чить размер массива отображения страниц". Формат области сохране-
         ния  контекста  зависит от внутренней реализации менеджера расши-
         ренной памяти и не предназначен для понимания  Вашей  программой.
         Даже  предполагая, что  Вы  можете определить положение регистров
         отображения страниц внутри области сохранения,  Вы не можете  на-
         дежно  определить,  какие  логические  страницы были отображены в
         каждую физическую страницу.
              Поскольку спецификация  расширенной памяти LIM 4.0 поддержи-
         вает до 36 физических страниц,  затраты памяти  на  сохранение  и
         восстановление  полного контекста могут существенно превысить то,
         что имело место для 64-Кбайтного кадра страниц спецификации  рас-
         ширенной памяти LIM 3.2.  Для сокращения этих затрат была опреде-

                                      - 7-45 -
         лена функция 16  "Получить/установить частичное отображение стра-
         ниц".  Программа  может  сохранить контекст только для конкретных
         физических страниц,  которые она будет изменять,  аналогично спо-
         собности программиста на языке ассемблера сохранять только те ре-
         гистры,  которые будут изменены программой обслуживания  прерыва-
         ния.  Подфункция  00h    "Получить частичное отображение страниц"
         сохраняет нуль или более регистров отображения страниц  в буфере,
         поставляемом пользователем. Подфункция 01h  "Установить частичное
         отображение страниц"   восстанавливает нуль или более  таких  ре-
         гистров.  Подфункция  02h   "Получить  размер  области сохранения
         отображения страниц"   возвращает размер области сохранения, тре-
         буемый  для сохранения контекста,  состоящего из указанного коли-
         чества физических страниц.

                                Переключение задач

              Явная поддержка переключения задач с помощью менеджера  рас-
         ширенной  памяти была добавлена в спецификации расширенной памяти
         LIM 4.0. Нижеописанный набор функций предназначен для использова-
         ния операционными системами или операционными средами такими, как
         DESQView фирмы Quarterdeck или  Windows  фирмы  Microsoft,  и  не
         должны  использоваться типичными прикладными программами специфи-
         кации расширенной памяти. Конкретные детали, касающиеся использо-
         вания этих функций,  выходят за рамки данной главы,  но некоторое
         обсуждение их целей и реализации целесообразно.
              Функции операционных  сред будут способны использовать преи-
         мущества усовершенствованных  аппаратурных  средств  спецификации
         расширенной памяти.  Одно из средств, которое может быть включено
         в новое поколение плат спецификации расширенной  памяти,  -  мно-
         жество  наборов регистров отображения.  Этим будет обеспечиваться
         практически мгновенное переключение контекста между двумя или бо-
         лее  задачами путем назначения разных наборов регистров отображе-
         ния для каждого контекста.  Еще одно средство, именуемое  "наборы
         регистров  ПДП", позволит многозадачным операционным системам пе-
         реключать задачи,  пока другая задача ожидает завершения передачи
         по ПДП. Поддержка многих наборов регистров отображения и одновре-
         менных передач ПДП включена в девять подфункций функции 28   "Из-
         менить набор регистров отображения".
              Программное обеспечение операционных сред  может определять,
         какие новые возможности аппаратуры поддерживаются данным менедже-
         ром расширенной памяти,  путем выдачи функции 26 "Получить инфор-
         мацию об аппаратуре расширенной памяти". Она возвращает количест-
         во  альтернативных   наборов   регистров   отображения,   наборов
         регистров  ПДП и индикатор способности аппаратуры расширенной па-
         мяти обнаруживать,  когда выполняется ПДП.  Также она  возвращает
         размер  исходных  страниц,  поддерживаемый менеджером расширенной
         памяти.
              Аппаратура, содержащая в себе эти средства,  только начинает
         появляться на рынке.  Чтобы позволить выполнять разработку много-
         задачного программного обеспечения до того, как аппаратура нового
         поколения станет легко доступной,  в спецификации расширенной па-
         мяти LIM 4.0 обеспечена программная имитация альтернативных набо-
         ров регистров отображения путем сохранения и  восстановления  об-
         ластей сохранения контекста, которая обеспечивается многозадачным
         монитором и находится внутри него.

                                      - 7-46 -
                               Неразрушаемая память

              Две функции,  добавленные в спецификации расширенной  памяти
         LIM 4.0, поддерживают сохранение расширенной памяти в течение пе-
         резапуска из памяти.  Программное обеспечение, которое отображает
         память  в  отображаемую  обычную память (память ниже 640К) должно
         перехватывать все условия,  ведущие к перезапуску из памяти (пре-
         рывание 19h BIOS), и выдавать функцию 29  "Подготовить аппаратуру
         расширенной памяти для перезапуска из памяти".  Платы расширенной
         памяти  с соответствующим оборудованием смогут сохранить содержи-
         мое отображаемой обычной памяти,  так же как и  текущий  контекст
         отображения на время перезапуска из памяти. Менеджеры расширенной
         памяти для существующих плат не реализуют данную опцию, поскольку
         платы зависят от схемы регенерации системной памяти, которая бло-
         кируется на время перезапуска из памяти.
              Функция 19  "Получить/установить  атрибут обработчика"  поз-
         воляет приложению определить,  поддерживает ли менеджер расширен-
         ной памяти возможность сохранения содержимого страниц обработчика
         в течение перезапуска из памяти. Если да, приложение может запро-
         сить,  чтобы  менеджер  расширенной  памяти или сохранил страницы
         указанного обработчика на время перезапуска из памяти путем уста-
         новки атрибута обработчика на неразрушаемость, или разрешил   ме-
         неджеру расширенной памяти освободить обработчик и  сбросить  со-
         держимое   связанных  страниц  во  время  перезапуска  из  памяти
         (разрушаемый обработчик). По умолчанию у всех обработчиков внача-
         ле атрибут установлен на разрушаемость.

                                Управление доступом

              Важной особенностью набора функций операционных сред являет-
         ся то,  что многозадачный менеджер может запретить доступ к функ-
         циям менеджера расширенной памяти,  ориентированным на операцион-
         ную систему,  для любой программы, кроме самого себя. Функция  30
         "Разрешить/запретить  набор функций операционных сред"  позволяет
         многозадачному менеджеру блокировать функции 26,  28 и  30  перед
         тем,  как он передаст управление прикладному программному обеспе-
         чению и разблокировать доступ для собственных нужд.

                                Заключение

              Для многих типов приложений  расширенная  память  предлагает
         практическое программное решение для 640-Кбайтного ограничения PC
         DOS.  Наиболее современным определением  программного  интерфейса
         между  приложением и механизмом управления памяти,  переключаемой
         банками,  является спецификация расширенной памяти LIM 4.0.  Этот
         программный интерфейс реализует менеджер расширенной памяти,  ко-
         торый обычно загружается как драйвер символьного  устройства  DOS
         во  время загрузки.  Системы расширенной памяти могут строиться с
         помощью сложного оборудования  спецификации  расширенной  памяти,
         механизма страниц процессора 80386 Intel или регистрами отображе-
         ния на некоторых типах плат расширения памяти  PS/2  или  системы
         расширенной памяти могут быть чисто программными.
              Приложения выдают запросы функций для  менеджера расширенной
         памяти через механизм программного прерывания 67h,  сходным обра-
         зом с интерфейсом прерывания 21h DOS.  Параметры передаются через
         регистры и/или структуры данных,  резидентные в памяти, механизм,
         наиболее естественный для программистов на языке ассемблера. При-

                                      - 7-47 -
         ложения,  написанные на языках высокого уровня, также могут обра-
         щаться к расширенной памяти,  если эти языки обеспечивают способы
         выдачи  программных прерываний,  обработки регистров процессора и
         определяют удаленные указатели для кодовых объектов и данных.
              Спецификация расширенной  памяти LIM 3.2 определяет 8-Мбайт-
         ное расширенное адресное пространство,  разбитое  на  16-Кбайтные
         страницы. До 64К из этого пространства может быть доступно однов-
         ременно через 64-Кбайтный кадр страниц,  размещенный в пространс-
         тве памяти над 640К. Также определяется набор из 14 функций отно-
         сительно низкого уровня,  которые могут применяться для доступа и
         обработки кодовых объектов и данных в расширенной памяти.  Эти 14
         функций могут быть разделены на три группы:  информационные,  уп-
         равления данными и управления контекстом.
              Спецификация расширенной памяти LIM 4.0 -  дополнение,  сов-
         местимое вверх,  спецификации 3.2,  которое сейчас включается как
         часть в MS-DOS версии 4.0.  Она содержит несколько средств,  при-
         сутствующих в усовершенствованной спецификации расширенной памяти
         AQA,  включая кадр страниц,  больший 64К, и способность поддержи-
         вать  отображаемую  память  ниже 640К.  Она также добавляет класс
         функций,  разработанных, чтобы непосредственно поддерживать быст-
         рое переключение задач многозадачными операционными средами. Спе-
         цификация 4.0 увеличивает адресное пространство расширенной памя-
         ти  до 32 Мбайт и предлагает набор функций более высокого уровня,
         чем те, которые были возможны в спецификации 3.2.
              Пользоваться расширенной  памятью  могут  как нерезидентные,
         так и резидентные приложения. Резидентные приложения должны поль-
         зоваться функциями управления контекстом для сохранения и восста-
         новления контекста менеджера расширенной памяти,  так  как  такие
         программы должны сохранять состояние процессора при входе и  вос-
         станавливать это состояние при выходе.
              Функции поддержки операционной системы спецификации 4.0 спо-
         собны пользоваться преимуществами  усовершенствованных аппаратных
         средств,  которые могут появиться в новых разработках расширенной
         памяти. Одним из таких средств является множество наборов отобра-
         жения страниц, которое позволяет многозадачной операционной среде
         почти мгновенно переключать контекст путем назначения набора  ре-
         гистров  отображения задаче.  Другое средство - неразрушаемая па-
         мять - позволит содержимому  расширенной  памяти  сохраняться  во
         время  перезапуска  из памяти.  Платы,  обеспечивающие аппаратную
         поддержку для этих функций, только теперь появляются в продаже.

                                    Литература

              Duncan, Ray.  "Lotus/Intel/Microsoft Expanded Memory",  Byte,
         11, no.11, 1986 (Специальное издание IBM).
              Как писать программы с помощью спецификации расширенной  па-
         мяти LIM 3.2.  Даны примеры - части программы RAMDISK,  в которой
         используется расширенная память.

              Hansen, Marion,  and  John  Driscoll.  "LIM   EMS   4.0:   A
         definition for the Next Generation of Expanded Memory", MSJ3, no.
         1, Jan.88.
              Описание средств, введенных в спецификации расширенной памя-
         ти LIM 4.0. Примеры программ на Си и языке ассемблера  показывают
         улучшенные методы для сохранения экрана,  разделения данных между
         программами и выполнения кода из расширенной памяти.


                                      - 7-48 -
              Hansen, Marion,  Bill Krueger,  and Nick Stueklen. "Expanded
         Memory:  Writing  Programs  That  Break the 640K Barrier",  MSJ2,
         no.1, Mar.87.
              Описание спецификации  расширенной  памяти LIM 3.2.  Примеры
         программ на С и языке ассемблера показывают, как выполнять сохра-
         нение экрана и исполнять код из расширенной памяти.

              Lefor, John  A.,  and  Karen  Lund.  "Reaching into Expanded
         Memory", PCTJ5, no.5, May 86.
              Рассмотрение спецификации  расширенной памяти LIM 3.2 и усо-
         вершенствованной спецификации расширенной памяти AQA,  ориентиро-
         ванное на приложения. Примеры законченных программ, которые полу-
         чают параметры  расширенной  памяти  и  распечатывают  данные  из
         расширенной памяти.

              Lotus/Intel/Microsoft. "Lotus/Intel/Microsoft       Expanded
         Memory Specification,  Version 4.0",  Document number  300275-05,
         Oct 87.
              Полная спецификация самой последней версии спецификации рас-
         ширенной  памяти.  Включает  примеры  программ на Турбо-Паскале и
         языке ассемблера.

              Mirecki, Ted.  "Expandable Memory", PCTJ4 no.2, Feb 86.
              Описание спецификации  расширенной  памяти LIM 3.2 и усовер-
         шенствованной спецификации расширенной памяти AQA.  Тесты изделий
         расширенной памяти фирм Intel и AST.

              Yao, Paul.  "EMS  Support  Improves  Microsoft  Windows  2.0
         Application Performance", MSJ3, no.1, Jan 88.
              Техническое рассмотрение  способа,  в  котором  в  программе
         Windows 2.0 используется спецификация расширенной памяти  LIM 4.0
         для поддержания многих параллельно работающих приложений.



                                      - 7-49 -

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

              Если данная глава выполнила свое назначение, то у Вас должно
         быть хорошее представление того, как работает расширенная память,
         и  как  программы DOS могут использовать мегабайты дополнительной
         памяти для кодов и данных. Теперь мы поможем Вам применять потен-
         циал расширенной памяти в Вашем собственном программном обеспече-
         нии  путем  снабжения  исчерпывающим набором листингов на языке С
         фирмы Microsoft версия 5.0. Мы также представим пример приложения
         спецификации расширенной памяти, в котором используются некоторые
         из более сложных функций расширенной памяти. В таблице 7-4 кратко
         перечислены листинги, которые Вы найдете в конце данной главы.

                                                         Таблица 7-4
                       Программы сопряжения на низком уровне
         ДДДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
        Лис-¦Имя файла ¦                      Содержание
        тинг¦          ¦
         ДДД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         7-2¦EMMCONST.H¦Обеспечивает #defines для общих  констант менед-
            ¦          ¦жера расширенной памяти
         7-3¦EMMTYPES.H¦Содержит typdefs (определения типов)  для структур
            ¦          ¦данных, передаваемых между функциями сопряжения
            ¦          ¦спецификации расширенной памяти  и  прикладными
            ¦          ¦программами спецификации расширенной памяти
         7-4¦EMMERMSG.C¦Дает массив символьных строк,обеспечивающих  крат-
            ¦          ¦кое  текстовое  описание для каждого ненулевого
            ¦          ¦кода состояния функции спецификации расширенной
            ¦          ¦памяти
         7-5¦EMMFUNC.C ¦Исчерпывающая библиотека функций спецификации  рас-
            ¦          ¦ширенной памяти. Если не определено иное, любая
            ¦          ¦функция спецификации расширенной памяти возвра-
            ¦          ¦щает  код состояния функции спецификации расши-
            ¦          ¦ренной памяти как целое
         7-6¦EMMFUNC.H ¦Содержит  прототипы  функций  для каждой функции в
            ¦          ¦EMMFUNC.С.  Если  Ваш  компилятор  поддерживает
            ¦          ¦прототипы функций,  определенные в спецификации
            ¦          ¦языка С ANSI (как версия  5  фирмы  Microsoft),
            ¦          ¦включение данного файла в Ваши приложения будет
            ¦          ¦гарантировать, что типы аргументов, указанные в
            ¦          ¦Ваших  программах  согласуются с типом парамет-
            ¦          ¦ров, ожидаемым вызванными функциями.
         7-7¦EMMEXIST.H¦Содержит программы тестирования наличия расширен-
            ¦          ¦ной памяти.  Метод "открытого обработчика"  вы-
            ¦          ¦полняется функцией emm_exists (строка 25).  Ме-
            ¦          ¦тод "получения вектора прерывания" тестирования
            ¦          ¦наличия расширенной памяти выполняется функцией
            ¦          ¦emm_exists2 (строка 113).
         7-8¦SNAPSHOT.C¦Обеспечивает  программу,  остающуюся  резидентной
            ¦          ¦после завершения, которая сохраняет текущее со-
            ¦          ¦держимое  экрана  дисплея в буфер в расширенной
            ¦          ¦памяти каждый  раз,  когда  нажимается  клавиша
            ¦          ¦PrtScr. Данная программа может сохранять столь-
            ¦          ¦ко образов экрана, сколько расширенной памяти в
            ¦          ¦Вашей системе.
         7-9¦PLAYBACK.C¦Обеспечивает программу для копирования образов эк-
            ¦          ¦ранов, сохраненных программой SNAPSHOT в расши-
            ¦          ¦ренной памяти, в стандартный выходной файл DOS.
            ¦          ¦Экранные изображения программы мо

                                      - 7-50 -
            ¦          ¦гут захватываться  постоянно,  например,  путем
            ¦          ¦перенаправления  стандартного  выходного  файла
            ¦          ¦PLAYBACK в дисковый файл.
        7-10¦BEEP.ASM  ¦Утилита для издания звука слышимого тона на встро-
            ¦          ¦енном динамике PC.
         ДДДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                               О примере приложения

              Пример приложения  состоит  из  двух программ:  SNAPSHOT.C и
         PLAYBACK.C. SNAPSHOT.C - программа, остающаяся в памяти резидент-
         но после завершения, которая сохраняет образы текстовых экранов в
         расширенной  памяти  путем  перехвата  прерывания  печати  экрана
         (int 5).  Данное прерывание выдается всякий раз, когда нажимается
         клавиша PrtScr или Print Screen.  SNAPSHOT также строит индексную
         структуру данных в расширенной памяти, которая содержит номер ло-
         гической страницы и смещение в байтах для каждого  образа экрана,
         сохраненного в расширенной памяти.  PLAYBACK просто считывает ин-
         дексную структуру данных,  копируя каждый из текстовых экранов из
         расширенной памяти в стандартный выходной файл DOS.  На рис.  7-4
         показано использование расширенной памяти для  установления связи
         между двумя независимыми программами.
              Для того чтобы продемонстрировать некоторые из более сложных
         идей расширенной памяти,  которые были представлены в данной гла-
         ве,  данное приложение с необходимостью является  более  сложным,
         чем мог бы ожидать программист среднего уровня от первого проекта
         приложения расширенной памяти.  Разработчики, которые незнакомы с
         программами, остающимися резидентными по завершении, и программи-
         рованием драйверов прерываний в среде DOS, могут ощущать особенно
         значительные    трудности,   воспринимая   большую   часть   кода
         SNAPSHOT.C, которая требуется для установки, управления и заверше-
         ния самой программы.
              В то же время в программе SNAPSHOT - не только доля усложне-
         ний,  связанных с DOS, использование переключения контекста и но-
         вых функций спецификации расширенной памяти  LIM  4.0  предлагает
         ценный  пример для изучения идей спецификации расширенной памяти,
         которые не всегда демонстрируются в руководствах по  программиро-
         ванию спецификации расширенной памяти. Ключевые средства специфи-
         кации расширенной памяти,  использованные в SNAPSHOT,  включают в
         себя:
              * Разделение расширенной памяти между программами  с помощью
         средства "Именования обработчика" (функция 20 спецификации расши-
         ренной памяти).
              * Переключение   контекста  с  помощью  "Получить/установить
         отображение страниц" (функция 15 спецификации  расширенной  памя-
         ти).
              * Перемещения блоков данных между обычной и расширенной  па-
         мятью  с  помощью  функции  "Передвинуть/обменять область памяти"
         (функция 18 спецификации расширенной памяти).
              * Динамическое  добавление  логических страниц к ранее разме-
         щенным для  обработчика спецификации расширенной памяти с помощью
         функции переразмещения страниц (функция 18 спецификации расширен-
         ной памяти).
              Примечание: Для выполнения этих программ Ваш менеджер расши-
         ренной памяти должен поддерживать спецификацию расширенной памяти
         LIM 4.0.


                                      - 7-51 -
                       Несколько соображений по кодированию

              Фактические действия  по сохранению экранов видеоотображения
         в расширенной памяти  тривиальны.  Единственного  вызова  функции
         спецификации расширенной памяти "Передвинуть/обменять область па-
         мяти" на строке 175 SNAPSHOT.C достаточно для  копирования  всего
         образа  экрана  из видеобуфера в расширенную память.  Так как это
         происходит внутри обработчика прерывания, функция "Получить отоб-
         ражение страниц" на строке 110 требуется для сохранения контекста
          SNAPSHOT.EXE ("после завершения остаться в памяти")
          ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
          ¦ ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬ ¦
          ¦ ¦inst_isr(вход при нажатии PrtScr)¦ ¦
          ¦ ¦ 1.Сохраняет состояние emm       ¦ ¦
          ¦ ¦ 2.Копирует образ экрана         ¦ ¦
          ¦ ¦   в расширенную память          ¦ ¦
          ¦ ¦ 3.Обновляет индекс образа эк-   ¦ ¦
          ¦ ¦   рана в расширенной памяти     ¦ ГДДД¬
          ¦ ¦ 4.Восстанавливает состояние emm ¦ ¦   ¦
          ¦ АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ¦   ¦
          ¦ ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬ ¦   ¦
          ¦ ¦main                             ¦ ¦   ¦
          ¦ ¦ 1.Начинает,завершает и сооб-    ¦ ¦   ¦
          ¦ ¦   щает состояние программы      ¦ ¦   ¦
          ¦ АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ¦   ¦
          АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ   ¦
                                                    ¦  Расширенная память
                               SNAPSHOT             ¦
                               ЪДДДДДДДДДДДДДДВДДДДДБДДДДДДВДДДДДДДДДДДД¬
                               ¦ Заголовок и  ¦Образ экрана¦Образ экрана¦
                             0 ¦ индекс экрана¦     1      ¦     2      ¦
                               ГДДВДДДДДДДДДДДБВДДДДДДДДДДДБДВДДДДДДДДДДґ
                               ¦  ¦Образ экрана¦   . . .     ¦  . . .   ¦
                             1 ¦  ¦     3      ¦             ¦          ¦
                               ГДДБДВДДДДДДДДДДБДДДДДДДДДДДДДБДДДДДДДДДДґ
                               ¦    ¦                                   ¦
                             2 ¦    ¦                                   ¦
                               ГДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                               ¦                                        ¦
                             3 ¦                                        ¦
                               ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                               ¦                                        ¦
                             4 ¦                                        ¦
                               ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                               ¦                                        ¦
                             5 ¦                                        ¦
                               АДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДЩ
                                                    ¦
          PLAYBACK.EXE                              ¦
          ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬       ¦
          ¦1.Обнаруживает расширенную память¦       ¦
          ¦2.Ищет идентификатор обработчика ¦       ¦
          ¦3.Отображает в индекс экрана     ¦       ¦
          ¦4.Отображает в каждый образ эк-  ГДДДДДДДЩ
          ¦  рана и копирует в стандартный  ¦
          ¦  выходной файл                  ¦
          АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
              Рис. 7-4. Пример приложения  - SNAPSHOT/PLAYBACK

                                      - 7-52 -
         отображения спецификации расширенной памяти.  Функция "Установить
         отображение  страниц"  на  строке 120 восстанавливает отображение
         страниц спецификации расширенной памяти,  которое имело место пе-
         ред входом в драйвер прерывания.
              Функция "Установить имя обработчика" на строке 372 SNAPSHOT.
         C  связывает имя в кодах ASCII "SNAPSHOT" с обработчиком специфи-
         кации расширенной памяти,  который данной программой размещен для
         сохранения  экрана.  Путем  задания  обработчику  имени программа
         PLAYBACK может определить положение и обратиться к данной области
         расширенной  памяти,  не зная действительного номера обработчика,
         значение которого может изменяться  каждый  раз,  когда  стартует
         SNAPSHOT.



                             Листинг 7-2. EMMCONST.H
        ------------------------------------------------------------------
        /*
        Общие константы спецификации расширенной памяти
        */

        #define EMM_INT             0x67        /*программное прерывание
                                                    расширенной памяти*/
        #define HANDLE_NAME_LENGTH  8           /*кол-во байт в имени об-
                                                  работчика*/
        #define PAGE_FRAMES         4           /*максимальное кол-во фи-
                                                  зических страниц*/
        #define PAGE_SIZE           16384       /*кол-во байтов в странице
                                         спецификации расширенной памяти*/
        #define EMM_DEVICE          "EMMXXXX0"  /*имя драйвера устройства
                                            менеджера расширенной памяти*/
        #define MAX_HANDLE          255         /*максимальное кол-во обра-
                                    ботчиков менеджера расширенной памяти*/
        /*
        Константы для кодов состояния спецификации расширенной памяти
        */

        #define FRSTEMERR           0x80        /*номер первой ошибки ме-
                                               неджера расширенной памяти*/
        #define LASTEMERR           0xA4        /*номер последн. ошибки ме-
                                               неджера расширенной памяти*/
        #define FUNCCOK             0x00        /*указанная функция завер-
                                                шилась без ошибок*/
        #define EMDRVSWF            0x80        /*программная ошибка драй-
                                        вера менеджера расширенной памяти*/
        #define EMDRVHWF            0x81        /*драйвер менеджера расши-
                                           ренной памяти обнаружил ошибку
                                                      в аппаратуре*/
        #define EMDRVBSY            0x82        /*драйвер менеджера расши-
                                                 ренной памяти      занят
                                                 (других не осталось)*/
        #define HANDLNFD            0x83        /*не найден указанный обра-
                                                  ботчик*/
        #define FUNCCUND            0x84        /*код функции неопределен*/
        #define HANDLINS            0x85        /*нет доступных обработчи-
                                                           ков*/
        #define MAPCXPRO            0x86        /*произошла ошибка восста-

                                      - 7-53 -
                                          новления контекста отображения*/
        #define TOTPGINS            0x87        /*не хватает страниц для
                                                  запроса*/
        #define UNAPGINS            0x88        /*не хватает размещенных
                                                  страниц для запроса*/
        #define LPAGE2SM            0x89        /*нуль логических страниц
                                          был запрошен от  функции,  сов-
                                          местимой  со спецификацией рас-
                                          ширенной памяти LIM 3.2*/
        #define LPAGERNG            0x8A        /*логическая страница вне
                                        диапазона указанного обработчика*/
        #define PPAGE2BG            0x8B        /*физическая страница вне
                                                      диапазона*/
        #define MRCSAFUL            0x8C        /*область сохранения кон-
                                             текста регистров отображения
                                             полна*/
        #define MRCSTDUP            0x8D        /*у стека контекста регист-
                                        ров отображения   уже  есть  кон-
                                       текст, связанный с указанным обра-
                                       ботчиком*/
        #define MRCSTNFD            0x8E        /*у стека контекста регист-
                                        ров отображения   нет  контекста,
                                         связанного с указанным  обработ-
                                         чиком*/
        #define SFUNCUND           0x8F        /*была запрошена неопреде-
                                                  ленная подфункция*/
        #define ATTRBUND            0x90      /*тип атрибута неопределен*/
        #define NVSTGUNS            0x91      /*система не поддерживает не-
                                                        разрушаемость*/
        #define MREGNOVW            0x92      /*во время передвижки области
                                 произошла частичная перезапись источника*/
        #define MRFGN2SM            0x93      /*область спецификации расши-
                                           ренной памяти  слишком  велика
                                                для указанного обработчи-
                                                ка*/
        #define MREGNOVL            0x94      /*область обычной памяти и
                                        область расширенной памяти перек-
                                                            рываются*/
        #define LPGOF2BG            0x95      /*смещение внутри логической
                                        страницы превышает  размер  логи-
                                                 ческой страницы*/
        #define MREGN2BG            0x96      /*длина области превосходит
                                                предел в 1 Мбайт*/
        #define MREGNDUP            0x97      /*область-источник и область-
                                        приемник расширенной памяти имеют
                                        один и тот же обработчик и перек-
                                        ры
                                                      ваются*/
        #define MREGNUND            0x98      /*неопределенный/неподдержи-
                                        ваемый типы   памяти-источника  и
                                                    приемника*/
        #define AMRSNFD             0x9A      /*указанный альтернативный
                                        набор регистров не существует*/
        #define AMDRSINS            0x9B      /*все альтернативные наборы
                                        регистров отображения/ПДП заняты*/
        #define AMDRSUNS            0x9C      /*альтернативные наборы ре-
                                        гистров отображения/ПДП  не  под-

                                      - 7-54 -
                                                            держиваются*/
        #define AMDRSUND            0x9D      /*указанный альтернативный
                                        набор регистров   отображения/ПДП
                                        не определен, не размещен или яв-
                                        ляется текущим набором*/

        #define DDMACUNS            0x9E      /*назначенные каналы ПДП не
                                                поддерживаются*/
        #define DDMACNFD            0x9F      /*назначенный указанный ка-
                                        нал  ПДП не существует*/
        #define HNDVLNFD            0xA0      /*не найдено значение, соот-
                                        ветствующее указанному имени  об-
                                                               работчика*
                                                               /
        #define HNDNMDUP            0xA1      /*обработчик с указанным
                                                 именем уже существует*/
        #define MREGNWRP            0xA2      /*попытка циклического пере-
                                        хода 1-Мбайтного адресного прост-
                                        ранства во время передвижки или
                                                         обмена*/
        #define USRDSFMT            0xA3      /*содержимое структуры дан-
                                        ных пользователя,      переданное
                                        функции искажено или бессмысленно
                                        */
        #define OPSYSACC            0xA4      /*операционная система зап-
                                        рещает доступ к данной функции*/
        ------------------------------------------------------------------


                             Листинг 7-3. EMMTYPES.H
        ------------------------------------------------------------------
        /*
        Структуры, используемые для связи с менеджером расширенной памяти
        */

        #define PCONTEXT            unsigned char
        #define PMAP                unsigned char

        typedef struct handle_page { /*структура  страницы  обработчика*/
                unsigned int emm_handle; /*размещенный обработчик
                                      менеджера расширенной памяти*/
                unsigned int pages_alloc_to_handle; /*логические страницы
                                               принадлежащие обработчику*/
        } HANDLE_PAGE;

        typedef struct ppmap { /*структура запроса частичного контекста*/
                unsigned int seg_cnt; /*количество отображаемых сег-
                                     ментов, которое требуется получить*/
                unsigned int seg_addr[PAGE_FRAMES];  /*адрес отображае-
                              мого сегмента, который требуется получить*/
        } PPMAP;


        typedef struct  log_to_phys  { /*структура отображения логических
                                                 на физические страницы*/

                unsigned int log_page_no;  /*номер логической  страницы*/

                                      - 7-55 -
                unsigned int phys_page_no; /*номер кадра страниц/адрес
                                                  отображаемого сегмента*/
        } LOG_TO_PHYS;

        typedef struct handle_names { /*элемент массива имен обработчика*/
                   unsigned int   handle_value;   /*обработчик*/
                    char handle_name[HANDLE_NAME_LENGTH]; /*имя, связанное
                                                           с обработчиком*/
        } HANDLE_NAMES;

        typedef struct  map_phys_page  { /*отображение отображаемого сег-
                                         мента в номер физическ. страницы
                                         */
                unsigned int phys_page_segment;  /*адрес  сегмента  физи-
                                                                 ческ.
                                                                 страни-
                                                                 цы*/
                unsigned int phys_page_number; /*номер физическ. страницы*/
        } MAP_PHYS_PAGE;

        typedef struct  hardware_info  { /*структура данных об аппаратуре
                                          спецификации расширенной памяти
                                          */
              unsigned int raw_page_size; /*кол-во байт в исх. странице*/
              unsigned int alt_reg_sets; /*кол-во альтернативных на-
                                          боров регистров отображения*/
                 unsigned int ctx_savearea_size;  /*кол-во байт в области
                                                    сохранения контекста*/
                 unsigned int dma_reg_sets;  /*кол-во  наборов  рег-ров
                                                ПДП*/
                 unsigned  int  dma_chan_op;  /*0:  работа  ПДП по
                                                станд. LIM,
                                                1: только один канал ПДП*/
        } HARDWARE_INFO;

        #define CONV_MEM           0        /*обычная память*/
        #define EXP_MEM            1        /*расширенная память*/

        typedef struct mregn { /*дескриптор области памяти*/

          unsigned char memory_type;       /*CONV_MEM / EXP_MEM*/

          unsignedint handle;       /*CONV_MEM: 0, EXP_MEM: обработчик*/

          unsigned int inutial_offset; /*CONV_MEM: 0 -65535,
                                        EXP_MEM:  0  -   16383*/
          unsigned int initial_seg_page;  /*CONV_MEM: адрес сегмента,
                                            EXP_MEM: номер страницы*/
        } MREGN;

        typedef struct move_xchg { /*структура  передвижки/обмена*/
           long region_length;    /*0 - 1 Мбайт*/
           MREGN source;          /*дискриптор области-источника*/
           MREGN dest;            /*дескриптор области -приемника*/
        } MOVE_XCHG;
        -------------------------------------------------------------------


                                      - 7-56 -
                             Листинг 7-4. EMMERMSG.C
        ------------------------------------------------------------------
        /*
        Наименование: emmermsg.c
        Содержание: сообщения  об  ошибках  для кодов ошибок спецификации
                    расширенной памяти LIM 4.0
        Ссылка: Lotus(r)/Intel(r)/Microsoft(r)  спецификация  расширенной
                    памяти, версия 4.0, стр.А5-А10
        */
        char *emmermsg[] = {
         "EMM driver  software  failure",                            ;  1
         "EMM  driver detected hardware failure",                    ;  2
         "EMM driver busy (doesn't happened any more)",              ;  3
         "Cannot  find  the  specified  handle",                     ;  4
         "The function  code  is  undefined",                        ;  5
         "No handles are currently available",                       ;  6
         "A  mapping  context  restoration  error  has occured",     ;  7
         "Insufficient  total  pages  for  request",                 ;  8
         "Insufficient  unallocated  pages  for  request",           ;  9
         "Zero logical pages have been requested from LIM 3.2
          compatible function",                                      ; 10
         "Logical page   out  of  range  for  specified  handle",    ; 11
         "Physical page out of range",                               ; 12
         "Mapping register  context save  area  is  full",           ; 13
         "Mapping  register context stack already has a context      ;
          associated with the specified handle",                     ; 14
         "Mapping register context stack does not have a  context    ;
          associated with the specified handle",                     ; 15
         "Undefined subfunction was  requested",                     ; 16
         "The  attribute type   is  undefined",                      ; 17
         "The  system  does  not  support nonvolatility",            ; 18
         "Partial source overwrite occured during move  region",     ; 19
         "Expanded  memory  region is too big for specified  handle",; 20
         "Conventional memory region and expanded memory region
         overlap",                                                   ; 21
         "Offset within a logical page exceeds the length of
          logical page",                                             ; 22
         "Region length   exceeds  1-Mbyte  limit",                  ; 23
         "Source  and destination expanded memory regions have the
          same handle and overlap",                                  ; 24
         "Undefined/unsupported memory source and destination types",; 25
         "Error code 0x99 is not used",                              ; 26
         "Specified alternate  map register  set  does  not  exist", ; 27
         "All alternate map/DMA register sets are in use",           ; 28
         "Alternate  map/DMA  register sets  are  not  supporte",    ; 29
         "Specified alternate map/DMA register set is not defined,
          not allocated, or is the current one",                     ; 30
         "Dedicated DMA  channels  are  not   supported",            ; 31
         "The specified  dedicated  DMA  channel does not exist",    ; 32
         "No corresponding handle value could be found for the
          specified handle name",                                    ; 33
         "A handle  with  the  specified  name  already  exists",    ; 34
         "Attempt to wrap around 1-Mbyte address space during
          move or exchange",                                         ; 35
         "The contents  of  the  user data structure passed to the
          function were corrupt or meaningless",                     ; 36
         "The operating system denied access to the function"        ; 37

                                      - 7-57 -
             };
        -------------------------------------------------------------------

        1 - программная ошибка драйвера менеджера расширенной памяти, 2 -
        драйвер менеджера расширенной памяти обнаружил ошибку в аппарату-
        ре, 3 - драйвер менеджера расширенной памяти занят (других не ос-
        талось),  4 - не найден указанный обработчик, 5 - код функции не-
        определен,  6 - нет доступных обработчиков,  7 - произошла ошибка
        восстановления контекста отображения,  8 - не хватает страниц для
        запроса,  9 - не хватает размещенных страниц для  запроса,  10  -
        нуль  логических страниц был запрошен от функции,  совместимой со
        спецификацией расширенной памяти LIM 3.2, 11 - логическая страни-
        ца вне диапазона указанного обработчика,  12 -физическая страница
        вне диапазона,  13 - область сохранения контекста регистров отоб-
        ражения  полна,  14 - у стека контекста регистров отображения уже
        есть контекст,  связанный с указанным обработчиком,  15 - у стека
        контекста регистров отображения нет контекста,  связанного с ука-
        занным обработчиком,  16 - была запрошена неопределенная подфунк-
        ция, 17 - тип атрибута неопределен, 18 - система не поддерживает
        неразрущаемость,  19 - во время передвижки области произошла час-
        тичная перезапись источника,  20 - область спецификации расширен-
        ной памяти слишком велика для указанного обработчика,  21  -  об-
        ласть  обычной памяти и область расширенной памяти перекрываются,
        22 - смещение внутри логической страницы превышает  размер  логи-
        ческой страницы, 23 - длина области превосходит предел в 1 Мбайт,
        24 - область-источник и область-приемник расширенной памяти имеют
        один и тот же обработчик и перекрываются, 25 - неопределенный/не-
        поддерживаемый типы памяти-источника и приемника, 26 - код ошибки
        0х99 не используется, 27 - указанный альтернативный набор регист-
        ров не существует, 28 - все альтернативные наборы регистров отоб-
        ражения/ПДП заняты,  29 - альтернативные наборы регистров отобра-
        жения/ПДП не поддерживаются,  30 - указанный альтернативный набор
        регистров отображения/ПДП не определен,  не размещен или является
        текущим набором, 31 -назначенные каналы ПДП не поддерживаются, 32
        - назначенный указанный канал ПДП не существует,  33 - не найдено
        значение,  соответствующее указанному имени обработчика, 34 - об-
        работчик  с указанным именем уже существует,  35 - попытка цикли-
        ческого перехода 1-Мбайтного адресного пространства во время  пе-
        редвижки   или   обмена,   36   -   содержимое  структуры  данных
        пользователя,   переданное  функции  искажено  или  бессмысленно,
        37 - операционная система запрещает доступ к данной функции


                              Листинг 7-5. EMMFUNC.C
        ------------------------------------------------------------------
        #include <dos.h>
        #include "emmconst.h"
        #include "emmtypes.h"
        #pragma check_stack(off)
        #define CONTINUE_COL 32    /*колонка продолж-я сообщ-я об ошибке*/
        static union REGS inregs, outregs;
        static struct SREGS segregs;
        static unt result;
        void ShowEMMErr(errcode, lineno, filename)
        unsigned int errcode;
        unsigned int lineno;
        char *filename;

                                      - 7-58 -


        {
             unsigned int ec, func, len, line;
             char *bp, *lp, *cp;
             extern char *emmermsg[];

             ec = errcode & 0x00FF;
             func = inregs.x.ax;
             printf("EMM error detected at line (%d) in source file(%s)\n",
                     lineno, filename);
             if (ec < FRSTEMER || ec > LASTEMER)
                printf("EMM Function (%04X) Error(%02X): Unknown Error
                   Code!\n", func, ec);
             else {
                printf("EMM Function (%04X) Error(%02X): ", func, ec);
                lp = emmermsg[ec-FRSTEMERR];
                line = 0;
                while (*lp) {
                   for (cp = lp, len = 80 - CONTINUE_COL; *cp && len;
                      cp++, len --)
                         if (*cp == ' ')
                            bp = cp;
                   if (*cp)
                      *bp++ = '\0';
                   if (line++)
                      printf("                                      ");
                   printf("%s\n", lp);
                   lp = (*cp) ? bp : cp;
                }
            }
        }

        EMGGetStatus() /*тестирует наличие работающего менеджера расширен-
                              ной памяти*/
        {
             inregs.h.ah = 0x40;      /*функция "Получить состояние" спе-
                                        цификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             return(result);
        }

        EMSGetFrameAddr(pfa)          /*возвращает удаленный адрес кадра
                                   страниц менеджера расширенной памяти*/
        char far **pfa;
        {
             inregs.h.ah = 0x41;      /*функция "Получить адрес кадра
                                   страниц" спецификации расширенной памя-
                                                         ти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             if (!result) {
                FP_SEG(*pfa) = outregs.x.bx;
                FP_OFF(*pfa) = 0;
             }
             return(result);
        }


                                      - 7-59 -
        EMSGetPageCnt(una, tot)       /*возвращает кол-во общих и неразме-
                                   щенных страниц спецификации расширенной
                                                               памяти*/
        unsigned int *una, *tot;
        {
             inregs.h.ah = 0x42;      /*функция "Получить кол-во неразме-
                                   щенных страниц" спецификации расширен-
                                                   ной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             if (!result) {
                        *una = outregs.x.bx;
                        *tot = outregs.x.dx;
             }
             return(result);
        }

        EMSAllocatePages(handle, pages)   /*размещает обработчик с 'pages'
                                                     логических страниц*/
        unsigned int *handle, pages;
        {
             inregs.h.ah = 0x43;      /*функция "Разместить страницы"
                                        спецификации расширенной памяти*/

             inregs.x.bx = pages;     /*кол-во логических страниц для
                                               размещения*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             if (!result)             /*функция завершилась успешно*/
                 *handle = outregs.x.dx;    /*обработчик   менеджера
                                          расширенной памяти для работы с
                                            этими страницами*/
             return(result);
        }

        EMSMapHandlePage(handle, page, frame)   /*отображает логическую
                                        страницу <handle, page> в 'кадр'*/
        unsigned int handle, page, frame;
        {
             inregs.h.ah = 0x44;      /*функция "Отобразить/перестать отоб-
                          ражать страницы спецификации расширенной памяти*/
             inregs.h.al = frame & 0x00ff;  /*кадр целевой страницы*/
             inregs.x.bx = page;     /*номер логической страницы, в которую
                                               отображать*/
             inregs.x.dx = handle;   /*обработчик, которому принадлежит
                                            логическая страница*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             return(result);
        }

        EMSDeallocatePages(handle)   /*освобождает обработчик и все его
                                                      страницы*/
        unsigned int handle;
        {
             inregs.h.ah = 0x45;      /*функция "Освободить страницы"
                                        спецификации расширенной памяти*/
             inregs.x.dx = handle;    /*обработчик, назначенный для осво-
                                   бождения менеджеру расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;

                                      - 7-60 -
             return(result);
        }

        EMSGetVersion(emsver)   /*возвращает номер версии программного
                               обеспечения менеджера расширенной памяти*/
        char *emsver;
        {
             inregs.h.ah = 0x46;      /*функция "Получить версию"
                                        спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs);
             if (!(result & 0xFF00)) {    /*функция завершилась успешно*/
                 emsver[0] = ((result & 0x00F0) >> 4) + '0';
                 emsver[1] = '.';
                 emsver[2] = (result & 0x000F) + '0';
                 emsver[3] = '\0';
             }
             return(result >> 8);
        }

        EMSSavePageMap(handle)   /*сохраняет контекст менеджера расширенной
                                  памяти в области сохранения контекста
                                   менеджера расширенной памяти*/
        unsigned int handle;
        {
             inregs.h.ah = 0x47;  /*функция "Сохранить отображение страниц"
                                          спецификации расширенной памяти*/
             inregs.x.dx = handle;    /*обработчик, для которого выполняет-
                                               ся сохранение*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             return(result);
        }

        EMSRestorePageMap(handle)   /*восстанавливает контекст менеджера
                                     расширенной памяти из области сохране-
                               ния контекста менеджера расширенной памяти*/
        unsigned int handle;
        {
             inregs.h.ah = 0x48;  /*функция "Восстановить отображение стра-
                                     ниц" спецификации расширенной памяти*/
             inregs.x.dx = handle;    /*область контекста, откуда выполня-
                                               ется восстановление*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             return(result);
        }

        EMSGetHandleCnt(hcnt)   /*возвращает кол-во открытых обработчиков
                                                     (1 - 255*/
        unsigned int *hсnt;
        {
             inregs.h.ah = 0x4B;      /*функция "Получить кол-во обработ-
                                  чиков спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             if (!result)    {        /*функция завершилась успешно*/
                 *hcnt = outregs.x.bx;
             }
             return(result);
        }

                                      - 7-61 -

        EMSGetHandlePages(handle, pages)   /*возвращает кол-во страниц,
                                            размещенных для обработчика*/
        unsigned int handle, *pages;
        {
             inregs.h.ah = 0x4С;      /*функция "Получить страницы обработ-
                                    чика" спецификации расширенной памяти*/
             inregs.x.dx = handle;    /*обработчик, которому, полагается,
                                               принадлежат страницы*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>>8;
             if (!result)             /*функция завершилась успешно*/
                 *pages = outregs.x.bx;
             return(result);
        }

        EMSGetAllHandlePages(hp, hpcnt)     /*возвращает кол-во страниц,
                                            размещенных всем обработчикам*/
        HANDLE_PAGE *hp;
        unsigned int *hpcnt;
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.h.ah = 0x4D;      /*функция "Получить страницы  всех
                            обработчиков" спецификации расширенной памяти*/
             segregs.es = segregs.ds;     /*сегмент массива HANDLE_PAGE*/
             inregs.x.di = (unsigned int) hp;   /*смещение массива HANDLE_
                                                               PAGE*/
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                             &segregs) >> 8;
             if (!result)             /*функция завершилась успешно*/
                 *hpcnt = outregs.x.bx;
             return(result);
        }

        EMSGetPageMap(map)   /*получает контекст менеджера расширенной па-
                        мяти в область сохранения контекста пользователя*/
        PMAP *map;
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             segregs.es = segregs.ds;     /*использовать es = ds*/
             inregs.x.ax = 0x4E00;    /*функция "Получить отображение стра-
                                     ниц" спецификации расширенной памяти*/
             inregs.x.di = (unsigned int) map;  /*указатель на массив отоб-
                                                            ражения*/
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }

        EMSSetPageMap(map)   /*устанавливает контекст менеджера расширенной
                      памяти из области сохранения контекста пользователя*/
        PMAP *map;
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax = 0x4E01;  /*функция "Установить отображение стра-
                                     ниц" спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) map;  /*указатель на массив отоб-
                                                            ражения*/

                                      - 7-62 -
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }

        EMSGetSetPageMap(srcmap, destmap)    /*сохраняет контекст менеджера
                           сширенной памяти в destmap и затем устанавливает
                          контекст менеджера расширенной памяти из srcmap*/
        PMAP *srcmap, *destmap;
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             segregs.es = segregs.ds;     /*оба отображения в ds*/
             inregs.x.ax = 0x4E02;  /*функция "Получить и установить отоб-
                         ражение страниц" спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) srcmap;  /*указатель на массив
                                                  отображения-источника*/
             inregs.x.di = (unsigned int) destmap;  /*указатель на массив
                                                  отображения-приемника*/
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }

        EMSGetPageMapeSize(size)   /*получает размер области сохранения
                                            контекста пользователя*/
        unsigned int *size;
        {
             inregs.x.ax = 0x4E03;    /*функция "Получить размер отображе-
                            ния страниц" спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs);
             if (!(result) & 0xFF00))      /*функция завершилась успешно*/
                 *size = outregs.h.al;
             return(result >> 8);
        }

        EMSGetPPageMap(pmap, savearea)   /*получает частичный контекст ме-
                                  неджера расширенной памяти в область со-
                                        хранения пользователя*/
        PPMAP *pmap;
        PCONTEXT *savearea;
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             segregs.es = segregs.ds;     /*использует es = ds*/
             inregs.x.ax = 0x4F00;  /*функция "Получить частичное отобра-
                           жение страниц" спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) pmap;  /*какие кадры мы хотим*/
             inregs.x.di = (unsigned int) savearea; /*указатель на массив
                                                              отображения*/
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                        &segregs) >> 8;
             return(result);
        }

        EMSSetPPageMap(savearea)    /*устанавливает частичный контекст ме-
                                  неджера расширенной памяти из области
                                        сохранения пользователя*/
        PCONTEXT *savearea;

                                      - 7-63 -
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax = 0x4F01;  /*функция "Установить частичное отобра-
                           жение страниц" спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) savearea; /*кадры, которые мы хо-
                                                      тим восстановить*/
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }

        EMSGetPPageMapeSize(count,size)   /*получает размер области, необ-
                                             ходимой для сохранения*/
        unsigned int count,*size;
        {
             inregs.x.ax =0x4F02;    /*функция "Получить размер частично-
                                          го отображения страниц" специфи-
                                                 кации расширенной памяти*/
             inregs.x.bx = count;    /*кол-во кадров для сохранения*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs);
             if (!(result) & 0xFF00))      /*функция завершилась успешно*/
                 *size = outregs.h.al;
             return(result >> 8);
        }

        EMSMapMultPages(handle, map, method, count)   /*отображает count
                                             страниц в map для handle*/
        unsigned int handle;     /*обработчик, для которого отображаются
                                                            страницы*/
        LOG_TO_PHYS *map;        /*отображение логических страниц в физи-
                                                             ческие*/
        unsigned int method;     /*используются номера кадра страниц или
                                      адреса отображаемых сегментов*/
        unsigned int count;      /*кол-во элементов в отображении*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.h.ah =0x50;      /*функция "Отобразить много страниц
                             обработчика" спецификации расширенной памяти*/
             inregs.h.al = (unsigned char) method;
             inregs.x.cx = count;    /*кол-во страниц для отображения*/
             inregs.x.dx = handle;   /*обработчик, которому эти страницы
                                                     принадлежат*/
             inregs.x.si = (unsigned int) map; /*страницы для отображения*/
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }

        EMSReallocPages(handle, pages)   /*изменяет размещение handle для
                                             pages*/
        unsigned int handle, *pages;
        {
             inregs.h.ah =0x51;      /*функция "Переразместить страницы"
                                          спецификации расширенной памяти*/
             inregs.x.bx = *pages;   /*кол-во логических страниц, которое
                                                   будет после выполнения*/
             inregs.x.dx = handle;   /*обработчик, для которого переразме-

                                      - 7-64 -
                                                   щается страница*/
           result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >> 8;
             if (!result)                   /*функция завершилась успешно*/
                 *pages = outregs.x.bx;     /*новое кол-во страниц*/
             return(result);
        }

        EMSGetHandleAttr(handle, attr)   /*получает атрибут обработчика*/
        unsigned int handle, *attr;
        {
             inregs.x.ax =0x5200;      /*функция "Получить атрибут обра-
                                  ботчика спецификации расширенной памяти*/
             inregs.x.dx = handle;
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs);
             if (!(result & 0xFF00))        /*функция завершилась успешно*/
                 *attr = outregs.h.al;      /*атрибут*/
             return(result >> 8);
        }

        EMSSetHandleAttr(handle, attr)   /*устанавливает атрибут обработ-
                                                                 чика*/
        unsigned int handle, attr;
        {
             inregs.x.ax =0x5201;      /*функция "Установить атрибут об-
                                работчика спецификации расширенной памяти*/
             inregs.x.dx = handle;
             inregs.h.bl = attr & 0x00FF;
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs)>> 8;
             return(result);
        }
        EMSGetAttrCap(cap)               /*получить возможности атрибута*/
        unsigned int *cap;
        {
             inregs.x.ax =0x5202;      /*функция "Получить возможности ат-
                                  рибута спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs);
             if (!(result & 0xFF00))     /*успех */
                   *cap = outregs.h.al;
             return(result >> 8);
        }
        EMSGetHandleName(handle, name)  /*получает имя обработчика handle*/
        unsigned int handle;    /*обработчик, для которого получается имя*/
        char *name;               /*буфер для получения имени обработчика*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5300;    /*функция "Получить имя обработчика"*/
             segregs.es = segregs.ds;
             inregs.x.di = (unsigned int) name;
             inregs.x.dx = handle;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }

        EMSSetHandleName(handle, name)  /*устанавливает имя обработчика
                                                                  handle*/
        unsigned int handle;     /*обработчик, для которого устанавл. имя*/

                                      - 7-65 -
        char *name;               /*буфер с именем обработчика*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5301;    /*функция "Установить имя обработч."*/
             inregs.x.si = (unsigned int) name;
             inregs.x.dx = handle;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                       &segregs) >> 8;
             return(result);
        }


        EMSGetHandleDir(hnt, hn_cnt)  /*получает имя каталога обработчика
                                                                  handle*/
        HANDLE_NAMES *hnt;       /*указатель на таблицу имен обработчиков*/
        unsigned int *hn_cnt;    /*возвращенное кол-во элементов*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5400;    /*функция "Получить каталог обработчи-
                                      ка" спецификации расширенной памяти*/
             inregs.x.di = (unsigned int) hnt;
             segregs.es = segregs.ds;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                            &segregs);
             if (!(result & 0xFF00))        /*функция завершилась успешно*/
                *hn_cnt = outregs.h.al;     /*возврат кол-ва полученных
                                                        имен обработчиков*/
             return(result >> 8);
        }

        EMSSearhHandleName(name, handle)  /*поиск названного  обработчика*/
        char *name;                       /*имя, которое нужно искать*/
        unsigned int *handle;            /*возвращаемый номер обработчика*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5401;    /*функция "Поиск названного обработчи-
                                      ка" спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) name;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                            &segregs) >> 8;
             if (!result)                   /*функция завершилась успешно*/
                *handle = outregs.x.dx;    /*возврат значения обработчика*/
             return(result);
        }

        EMSGetTotalHandles(handle_count)  /*Получить общее кол-во  обработ-
                                                                    чиков*/
        unsigned int *handle_count;
        {
             inregs.x.ax =0x5402;       /*подфункция "Получить общее кол-во
                            обработчиков" спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             if (!result)
                *handle_count = outregs.x.bx;
             return(result);
        }


                                      - 7-66 -
        EMSMoveRegion(rp)                          /*передвинуть область*/
        MOVE_XCHG *rp;                 /*указатель на дескриптор области*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5700;           /*функция "Передвинуть область"
                                          спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) rp;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                            &segregs) >> 8;
             return(result);
        }

        EMSExchangeRegion(rp)                         /*обменять область*/
        MOVE_XCHG *rp;                 /*указатель на дескриптор области*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5701;              /*функция "Обменять область"
                                          спецификации расширенной памяти*/
             inregs.x.si = (unsigned int) rp;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                            &segregs) >> 8;
             return(result);
        }

        EMSGetMapAddrArray(mpaa, mpa_cnt)   /*получить массив отображаемых
                                               физических адресов*/
        MAP_PHYS_PAGE *mpaa;           /*указатель на массив отображаемых
                                               физических адресов*/
        unsigned int *mpa_cnt;         /*кол-во возвращенных элементов*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5800;              /*функция "Получить массив
                                         отображаемых физических адресов"*/
             inregs.x.di = (unsigned int) mpaa;
             segregs.es = segregs.ds;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                            &segregs) >> 8;
             if (!result) >> 8);            /*функция завершилась успешно*/
                *mpa_cnt = outregs.x.cx;    /*возврат кол-ва отображаемых
                                                  физических страниц*/
             return(result);
        }

        EMSGetMapAddrCount(mpa_cnt)   /*получить кол-во отображаемых
                                               физических адресов*/
        unsigned int *mpa_cnt;         /*кол-во отображаемых физических
                                                             страниц*/
        {
             inregs.x.ax =0x5801;              /*функция "Получить кол-во
                                         отображаемых физических адресов"*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             if (!result)                   /*функция завершилась успешно*/
                *mpa_cnt = outregs.x.cx;    /*возврат кол-ва отображаемых
                                                  физических страниц*/
             return(result);
        }


                                      - 7-67 -
        EMSGetHardwareInfo(hwp)            /*получить информацию об обору-
                                 довании спецификации расширенной памяти*/
        HARDWARE_INFO *hwp;            /*указатель на область для получе-
                                        ния информации об оборудовании*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5900;      /*функция "Получить информацию  об
                             оборудовании спецификации расширенной памяти*/
             inregs.x.di = (unsigned int) hwp;
             segregs.es = segregs.ds;
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                            &segregs) >> 8;
             return(result);
        }

        EMSGetRawPageCount(rpg_cnt, urpg_cnt)  /*получить кол-во исходных
                                                                  страниц*/
        unsigned int *rpg_cnt;                  /*кол-во исходных страниц*/
        unsigned int *urpg_cnt;   /*кол-во неразмещенных исходных страниц*/
        {
             inregs.x.ax =0x5901;      /*функция "Получить кол-во исходных
                                  страниц спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             if (!result)   {               /*функция завершилась успешно*/
                *rpg_cnt = outregs.x.dx;  /*общее кол-во исходных страниц*/
                *urpg_cnt = outregs.x.bx; /*кол-во неразмещенных исходных
                                                            страниц*/
             }
             return(result);
        }

        EMSAllocateStdPages(handle, pages)  /*размещает обработчик с 'pag-
                                             es' стандартных страниц*/
        unsigned int handle, *pages;
        {
             inregs.x.ax =0x5A00;    /*функция "Разместить стандартные
                               страницы" спецификации расширенной памяти*/
             inregs.x.bx = pages;    /*кол-во логических страниц для раз-
                                                         мещения*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >> 8;
             if (!result)                  /*функция завершилась успешно*/
                *handle = outregs.x.dx;  /*обработчик менеджера расширенной
                                 памяти для применения с этими страницами*/
             return(result);
        }

        EMSAllocateRawPages(handle, pages)  /*размещает обработчик с 'pag-
                                                     es' исходных страниц*/
        unsigned int handle, *pages;
        {
             inregs.x.ax =0x5A01;    /*функция "Разместить исходные стра-
                                   ницы" спецификации расширенной памяти*/
             inregs.x.bx = pages;    /*кол-во логических страниц для раз-
                                                         мещения*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >> 8;
             if (!result)                  /*функция завершилась успешно*/
                *handle = outregs.x.dx;  /*обработчик менеджера расширенной

                                      - 7-68 -
                                 памяти для применения с этими страницами*/
             return(result);
        }

        EMSGetAltMapRegSet(set,pmap)  /*получает альтернативный набор ре-
                      гистров отображения спецификации расширенной памяти*/
        unsigned int *set;            /*текущий альтернативный набор ре-
                                                   гистров отображения*/
        PMAP far **pmap;              /*указатель на указатель области со-
                                                       хранения контекста*/
        {
             inregs.x.ax =0x5B00;    /*функция "Получить альтернативный на-
                                               бор регистров отображения"*/
             segread(&segregs);

             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                        &segregs) >> 8;
             if (!result)    {
                *set = outregs.h.bi;             /*текущий активный набор*/
                if (*set == 0)   {     /*фальшивый альтернативный набор ре-
                                                         гистров*/
                   FP_OFF(*pmap) = outregs.x.di;   /*смещение области кон-
                                         текста операционной среды (OS)*/
                   FP_SEG(*pmap) = segregs.es;     /*сегмент  области кон-
                                         текста операционной среды (OS)*/
                }
             }
             return(result);
        }

        EMSSetAltMapRegSet(set,pmap)  /*устанавливает альтернативный набор
                    регистров отображения спецификации расширенной памяти*/
        unsigned int set;             /*новый альтернативный набор ре-
                                                   гистров отображения*/
        PMAP *pmap;              /*указатель области сохранения контекста*/
        {
             segread(&segregs);           /*заполнить  регистры сегментов*/
             inregs.x.ax =0x5B01;  /*функция "Установить альтернативный на-
                                               бор регистров отображения"*/
             inregs.h.bl = set & 0x00FF;
                if (set == 0)   {     /*фальшивый альтернативный набор ре-
                                                         гистров*/
                   inregs.x.di = (unsigned int) pmap;
                   segregs.es = segregs.ds;
                }
             result = (unsigned int) int86x(EMM_INT, &inregs, &outregs,
                        &segregs) >> 8;
             if (!result)
       }

        EMSGetAltMapArraySize(size)  /*получает размер массива сохранения
                                              альтернативного отображения*/
        unsigned int size;             /*кол-во отображаемых физических
                                                   страниц*/
        {
             inregs.x.ax =0x5B02;  /*функция "Получить размер массива со-
                                    хранения альтернативного отображения"*/

                                      - 7-69 -
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             if (!result)                  /*успех*/
                *size = outregs.x.dx;             /*размер массива*/
             return(result);
        }

        EMSAllocAltMapRegSet(set)  /*размещает альтернативный набор регис-
                                                      тров отображения*/
        unsigned int *set;             /*номер, размещенного набора*/
        {
             inregs.x.ax =0x5B03;  /*функция "Разместить альтернативный
                                    набор регистров отображения"*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             if (!result)                  /*успех*/
                *set = outregs.h.bl;          /*номер размещенного набора*/
             return(result);
        }

        EMSDeallocAltMapRegSet(set)  /*освобождает альтернативный набор ре-
                                                      гистров отображения*/
        unsigned int set;             /*номер, освобождаемого набора*/
        {
             inregs.x.ax =0x5B04;  /*функция "Освободить альтернативный
                                            набор регистров отображения"*/
             inregs.h.bl = set & 0x00FF;
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             return(result);
        }
        EMSAllocDMARegSet(set)           /*размещает набор регистров ПДП*/
        unsigned int *set;                  /*номер, размещаемого набора*/
        {
             inregs.x.ax =0x5B05;  /*функция "Разместить  набор  регистров
                                    ПДП" спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             if (!result)                  /*успех*/
                *set = outregs.h.bl;          /*номер размещенного набора*/
             return(result);
        }

        EMSEnableDMARegSet(set, channel) /*разрешает набор регистров ПДП*/
        unsigned int set;                 /*номер, разрешаемого набора*/
        unsigned int channel;             /*номер канала ПДП для связыва-*/
                                             ния с регистром отображения*/
        {
             inregs.x.ax =0x5B06;       /*функция "Разрешить  набор  регис-
                                тров ПДП" спецификации расширенной памяти*/
             inregs.h.bl = set & 0x00FF;
             inregs.h.dl = channel & 0x00FF;
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             return(result);
        }

        EMSDisableDMARegSet(set)         /*запрещает набор регистров ПДП*/
        unsigned int set;                 /*номер запрещаемого набора*/
        {
             inregs.x.ax =0x5B07;    /*функция "Запретить  набор  регистров
                                     ПДП" спецификации расширенной памяти*/

                                      - 7-70 -
             inregs.h.bl = set & 0x00FF;
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             return(result);
        }

        EMSDDeallocDMARegSet(set)    /*освобождает набор регистров ПДП*/
        unsigned int set;                 /*номер освобождаемого набора*/
        {
             inregs.x.ax =0x5B08;   /*функция "Освободить  набор  регистров
                                     ПДП" спецификации расширенной памяти*/
             inregs.h.bl = set & 0x00FF;
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             return(result);
        }

        EMSPrepareForWarmboot()     /*подготавливает оборудование специфи-
                      кации расширенной памяти к программному перезапуску*/
        {
             inregs.h.ah =0x5C;     /*функция "Подготовиться к программному
                             перезапуску" спецификации расширенной памяти*/
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             return(result);
        }

        EMSEnableOSFunc(key)         /*разрешить набор функций операционной
                                                                    среды*/
        long *key;      /*ключ доступа к операционной среде - должен быть
                                    равен 0, когда применяется первый раз*/
        {
             inregs.x.ax =0x5D00;   /*функция "Разрешить набор функций опе-
                         рационной среды" спецификации расширенной памяти*/
             if (*key !=0)   {
                 inregs.x.bx = FP_OFF(*key);
                 inregs.x.cx = FP_SEG(*key);
        }
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
                if (!result)   {
                   FP_OFF(*key) = outregs.x.bx;
                   FP_SEG(*key) = outregs.x.cx;
                }
             }
             return(result);
        }

        EMSDisableOSFunc(key)         /*запретить набор функций операционной
                                                                    среды*/
        long *key;      /*ключ доступа к операционной среде - должен быть
                                    равен 0, когда применяется первый раз*/
        {
             inregs.x.ax =0x5D01;   /*функция "Запретить набор функций опе-
                         рационной среды" спецификации расширенной памяти*/
             if (*key !=0)   {
                 inregs.x.bx = FP_OFF(*key);
                 inregs.x.cx = FP_SEG(*key);
        }
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;


                                      - 7-71 -
                if (!result)   {
                   FP_OFF(*key) = outregs.x.bx;
                   FP_SEG(*key) = outregs.x.cx;
                }
             }
             return(result);
        }

        EMSReturnAccessKey(key)    /*возвращает ключ доступа к операционной
                                       среде менеджеру расширенной памяти*/
        long *key;                    /*ключ доступа к операционной среде*/
        {
             inregs.x.ax =0x5D02;      /*функция "Возвратить ключ доступа к
                      операционной среде" спецификации расширенной памяти*/
                 inregs.x.bx = FP_OFF(key);
                 inregs.x.cx = FP_SEG(key);
             result = (unsigned int) int86(EMM_INT, &inregs, &outregs) >>8;
             return(result);
        }
        ------------------------------------------------------------------

                              Листинг 7-6. EMMFUNC.H
        ------------------------------------------------------------------
        extern void ShowEMMErr(unsigned int errcode,unsigned int lineno,
                   char *filename);
        extern int EMSGetStatus(void);
        extern int EMSGetFrameAddr(char far * *pfa);
        extern int EMSGetPageCnt(unsigned int *una,unsigned int *tot);
        extern int EMSAllocatePages(unsigned int *handle,unsigned int pages);
        extern int EMSMapHandlePage(unsigned int handle,unsigned int page,
                   unsigned int frame);
        extern int EMSDeallocatePages(unsigned int handle);
        extern int EMSGetVersion(char *emsver);
        extern int EMSSavePageMap(unsigned int handle);
        extern int EMSRestorePageMap(unsigned int handle);
        extern int EMSGetHandleCnt(unsigned int *hcnt);
        extern int EMSGetHandlePages(unsigned int handle,unsigned int
                   *pages);

        extern int EMSGetAllHandlePages(struct handle_page *hp,unsigned int
                   *hpcnt);
        extern int EMSGetPageMap(unsigned int *map);
        extern int EMSSetPageMap(unsigned int *map);
        extern int EMSGetSetPageMap(unsigned int *srcmap,unsigned int
                   *destmap);
        extern int EMSGetPPageMapSize(unsigned int *size);
        extern int EMSGetPPageMap(struct ppmap *pmap,unsigned char
                   *savearea);
        extern int EMSSetPPageMap(unsigned char *savearea);
        extern int EMSGetPPageMapSize(unsigned int count,unsigned int
                   *size);
        extern int EMSMapMultPages(unsigned int handle,struct log_to_phys
                   *map,unsigned int method,unsigned int count);
        extern int EMSReallocaPages(unsigned int handle,unsigned int
                   *pages);
        extern int EMSGetHandleAttr(unsigned int handle,unsigned int
                   *attr);

                                      - 7-72 -
        extern int EMSSetHandleAttr(unsigned int handle,unsigned int
                    attr);
        extern int EMSGetAttrCap(unsigned int *cap);
        extern int EMSGetHandleName(unsigned int handle,char *name);
        extern int EMSSetHandleName(unsigned int handle,char *name);
        extern int EMSGetHandleDir(struct handle_names *hnt,unsigned int
                   *hn_cnt);
        extern int EMSSearchHandleName(char *name,unsigned int *handle);
        extern int EMSGetTotalHandles(unsigned int *handle_count);
        extern int EMSMoveRegion(struct move_xchg *rp);
        extern int EMSExchangeRegion(struct move_xchg *rp);
        extern int EMSGetMapAddrArray(struct map_phys_page *mpaa,unsigned
                   int *mpa_cnt);
        extern int EMSGetMapAddrCount(unsigned int *mpa_cnt);
        extern int EMSGetHardwareInfo(struct hardware_info *hwp);
        extern int EMSGetRawPageCount(unsigned int *rpg_cnt,unsigned int
                   *urpg_cnt);
        extern int EMSAllocateStdPages(unsigned int *handle,unsigned int
                   pages);
        extern int EMSAllocateRawPages(unsigned int *handle,unsigned int
                   pages);
        extern int EMSGetAltMapRegSet(unsigned int *set,unsigned char far
                   * *pmap);
        extern int EMSSetAltMapRegSet(unsigned int set,unsigned char
                   *pmap);
        extern int EMSGetAltMapArraySize(unsigned int *size);
        extern int EMSAllocAltMapRegSet(unsigned int *set);
        extern int EMSDeallocAltMapRegSet(unsigned int set);
        extern int EMSAllocDMARegSet(unsigned int *set);
        extern int EMSEnableDMARegSet(unsigned int set,unsigned int
                   channel);
        extern int EMSDisableDMARegSet(unsigned int set);
        extern int EMSDeallocDMARegSet(unsigned int set);
        extern int EMSPrepareForWarmboot(void);
        extern int EMSEnableOSFunc(long *key);
        extern int EMSDisableOSFunc(long *key);
        extern int EMSReturnAccessKey(long key);
        -------------------------------------------------------------------


                    Листинг 7-7. EMMEXIST.C
        ------------------------------------------------------------------
        #include <stdlib.h>
        #include <fcntl.h>
        #include <dos.h>
        #include <errno.h>
        #include "emmconst"

        #define DOS_INT           0x21     /*диспетчер функций DOS*/
        #define DOS_IOCTL         0x44     /*функция IOCTL DOS*/
        #define IOCTL_GETINFO     0x00     /*подфункция "Получить информа-
                                                 цию об устройстве" IOCTL*/
        #define IOCTL_OUTSTAT     0x07     /*подфункция "Получить состояние
                                                        вывода" IOCTL*/
        #define READY_OUTPUT      0xFF     /*устройство готово к выводу*/
        #define IS_DEVICE         0x0080   /*обработчик принадлежит устрой-
                                                                    ству*/

                                      - 7-73 -
        static char device_name[9] = EMM_DEVICE;

             /*
             Контролируется наличие расширенной памяти с помощью метода
             "открытого обработчика". Устанавливает emm_present в '1',
             если расширенная память присутствует, в '0' - если нет.
             Функция возвращает '0', если тест наличия завершился успеш-
             но. В противном случае возвращается код ошибки DOS вызова
             функции DOS, которая не выполнилась во время теста наличия
             расширенной памяти.
             */

        emm_exists(emm_present)
        int *emm_present;               /*указатель на индикатор наличия
                                             менеджера расширенной памяти*/
        {
             int return_code;            /*код возврата операции с файлом*/
             int handle;                 /*обработчик файлов*/
             unsigned int dev_attr;       /*атрибуты драйвера устройства*/
             unsigned int dev_status;     /*выходное состояние устройства*/

             if (_dos_open(device_name, O_RDONLY, &handle))  {
                                                /*не удалось открыть файл*/
                if (errno == ENOENT)  {              /*файл не существует*/
                   return_code = 0;             /*мы ожидали, что это могло
                                                               случиться*/
                   *emm_present = 0;          /*менеджер расширенной памяти
                                                  определенно отсутствует*/
                } else
                   return_code = errno;       /*тест наличия завершился
                                                               безуспешно*/
             } else
                if (!(return_code = ioctl_getattr(handle, &dev_attr))) {
                                                       /*получен атрибут*/
                   if (!(return_code = ioctl_outstat(handle, &dev_status)))
                                            /*получено выходное состояние*/
                                  /*менеджер расширенной памяти присутству-
                                    ет, если обработчик принадлежит устрой-
                                             ству и готов к выводу*/
                *emm_present = ((dev_status == READY_OUTPUT) && (dev_attr
                                           & IS_DEVICE)) ? 1 : 0;
                close(handle);             /*закрывается обработчик файла*/
             }
             return(return_code);
        }

             /*
             Получается слово атрибутов DOS для открытого обpаботчика,
             связанного с открытым файлом или устройством. Возвращает 0,
             если функция завершилась успешно, в противном случае воз-
             вращает код ошибки DOS
             */

        ioctl_getattr(handle, attrib)
        int handle;                /*открытый обpаботчик файла/устройства*/
        unsigned int *attrib; /* -> возвращенная информация об устройстве*/
        {

                                      - 7-74 -
             int rc;
             union REGS regs;
             regs.h.ah = DOS_IOCTL;  /*управление вводом/выводом  DOS  для
                                                                устройств*/
             regs.h.al = IOCTL_GETINFO;  /*получить информацию об устройс-
                                                                     тве*/
             regs.x.bx = handle;
             int86(DOS_INT, &regs, &regs);            /*вызов функции DOS*/
             if (!regs.x.cflag)  {             /*если не произошло ошибки*/
                *attrib = regs.x.dx;               /*возвращаются атрибуты
                                                         файла/устройства*/
                rc = 0;                     /*функция выполнилась успешно*/
             } else
                rc = regs.x.ax;             /*возвращается код ошибки*/
             return(rc);
        }

             /*
             Получает выходное состояние файла или устройства. Возвра-
             щенное состояние 0 означает, что устройство неготово для
             вывода; состояние 0xFF00 означает, что устройство готово
             для вывода. Возвращается 0, если функция завершилась ус-
             пешно. В противном случае возвращается код ошибки DOS.
             */

        ioctl_outstat(handle, status)
        int handle;                /*открытый обpаботчик файла/устройства*/
        unsigned int *status;                /* -> слово состояния вывода*/
        {
             int rc;
             union REGS regs;
             regs.h.ah = DOS_IOCTL;  /*управление вводом/выводом  DOS  для
                                                                устройств*/
             regs.h.al = IOCTL_OUTSTAT;       /*получить состояние вывода*/
             regs.x.bx = handle;
             int86(DOS_INT, &regs, &regs);            /*вызов функции DOS*/
             if (!regs.x.cflag)  {             /*если не произошло ошибки*/
                *status = regs.h.al;               /*возвращается состояние
                                                                   вывода*/
                rc = 0;                     /*функция выполнилась успешно*/
             } else
                rc = regs.x.ax;             /*возвращается код ошибки*/
             return(rc);
        }

             /*
             Проверяет наличие pасшиpенной памяти с помощью метода
             "получить вектор". Устанавливает emm_present в '1', если
             расширенная память присутствует, '0' - если нет. Данная
             функция всегда возвращает '0'.
             */

        emm_exists2(emm_present)
        int *emm_present;
        {
             int len;
             char far *device_name;        /*указатель на имя символьного

                                      - 7-75 -
                                                               устройства*/
             char *np;
             unsigned int get_int_seg();

             FP_SEG(dev_name) = get_int_seg(EMM_INT);   /*сегмент драйвера
                                  устройства менеджеpа pасшиpенной памяти*/

             FP_OFF(dev_name) = 10;      /*смещение имени драйвера символь-
                                                          ного устройства*/

             /*увидеть, находится ли имя менеджеpа pасшиpенной памяти
                       по смещению 10 в сегменте EMM_INT*/

             for (len  = 8, np = device_name; len && *dev_name++ == *np++;
                                                               len--);

             *emm_present = (len) ? 0 : 1;       /*если все символы совпа-
                              ли менеджеp pасшиpенной памяти присутствует*/
             return(0);                      /*всегда завершается успешно*/
        }

             /*
             Возвращает адрес сегмента вектора прерываний "intno"
             */

        unsigned int get_int_seg(intno)
        int intno;
        {
             union REGS regs;
             struct SREGS segregs;

             regs.h.al = (unsigned char) intno;
             regs.h.ah = 0x35;            /*функция "Получить вектор" DOS*/
             intdosx(&regs, &regs, &segregs);
             return((unsigned) segregs.es);
        }
        -------------------------------------------------------------------

                    Листинг 7-8. SNAPSHOT.C
        -------------------------------------------------------------------
             /*
                Наименование: SNAPSHOT.C
                Назначение:   утилита, остающаяся резидентно в памяти после
                              завершения, для сохранения образов текстовых
                              экранов в pасшиpенной памяти.
             */

        #include <stdio.h>
        #include <stdlib.h>
        #include <signal.h>
        #include <dos.h>
        #include <bios.h>

        #include "emmconst.h"    /*константы менеджеpа pасшиpенной памяти*/
        #include "emmtypes.h" /*структуры данных менеджеpа pасшиpенной па-
                                                                     мяти*/
        #include "emmerr.h"    /*коды ошибок менеджеpа pасшиpенной памяти*/

                                      - 7-76 -
        #include "emmfunc.h"  /*объявления  функций  менеджеpа pасшиpенной
                                                                   памяти*/

        #define PRTSC_INT          5           /*прерывание печати экрана*/
        #define HANDLE_NAME        "SNAPSHOT"  /*имя обpаботчика pасшиpен-
                                                               ной памяти*/
        #define MAX_SCR            500         /*максимальное кол-во сохра-
                                                           няемых экранов*/
        #define MDA_SEG            0xB000      /*сегмент буфера адаптера
                                                     монохромного дисплея*/
        #define CGA_SEG            0xB800      /*сегмент буфера адаптера
                                            цветного графического дисплея*/
        #define SCR_ROWS           25          /*предполагается 25 строк -
                                              процедура должна определить*/
                                               /*фактическое кол-во строк
                                                 зависит от адаптера*/

        #pragma pack(1)         /*структура данных, выровненная по байтам*/

        #define DisplayError(rc)      ShowEMMErr(rc, __LINE__, __FILE__)

        typedef struct bios_video_data  {     /*базисные видеоданные BIOS*/
             unsigned char   crt_mode;         /*режим дисплея*/
             unsigned int    crt_cols;         /*кол-во колонок на экране*/
             unsigned int    crt_len;          /*длина буфера регенерации в
                                                                   байтах*/
             unsigned int    crt_start;        /*начальный адрес в буфере
                                                           регенерации*/
             unsigned int    cursor_pos[8];    /*положение курсора для 8
                                                           страниц*/
             unsigned int    cursor_mode;      /*текущая установка режима
                                                                курсора*/
             unsigned char   active_page;      /*текущая отображаемая стра-
                                                                    ница*/
             unsigned int    addr_6845;        /*адрес базы активной платы
                                                            отображения*/
             unsigned char   crt_mode_set;     /*текущая установка регистра
                                                                     3х8*/
             unsigned char   crt_palette;      /*текущая установка палитры
                                                - цветная плата*/
        } BIOS_VIDEO_DATA;

        typedef struct scr  {                  /*дескриптор данных экрана*/
             unsigned int scr_page;            /*начальная страница pасши-
                                                pенной памяти для экранов*/
             unsigned int    scr_offset;       /*начальное смещение расши-
                                                ренной памяти для экранов*/
             unsigned int    scr_width;        /*кол-во колонок на экране*/
             unsigned int    scr_len;          /*длина экрана в байтах*/
        } SCR;

        typedef struct scr_index {           /*индексная структура экрана*/
             void (interrupt far *scr_int5)(); /*указатель на нашу програм-
                                               му обслуживания прерывания*/
             unsigned int    scr_count;        /*кол-во экранов, сохранен-
                                                ных в текущий момент*/
             unsigned int scr_max;            /*максимальное кол-во экранов

                                      - 7-77 -
                                                         для сохранения*/
        } SCR_INDEX;

             /*
                     глобальные данные
             */

        void (interrupt far *old_int5)();   /*старый вектор печати экрана*/
        PMAP *emm_save;                     /*указатель на область сохране-
                              ния контекста менеджеpа pасшиpенной памяти*/
        unsigned int emm_tpages,            /*общее кол-во страниц pасши-
                                                            pенной памяти*/
                         emm_apages,        /*доступное кол-во страниц
                                                      pасшиpенной памяти*/
                         emm_handle,        /*обpаботчик pасшиpенной памя-
                                                                       ти*/
                         emm_pages,         /*кол-во страниц, принадлежащих
                                                              обpаботчику*/
                         isr_status,        /*0: isr следует сцепить,
                                            <>0: isr следует обслужить*/
                         terminate_flag;    /*1: закончить эту программу*/
        char   far       *page_frame;     /*удаленная -> в кадровый буфер*/
        SCR_INDEX        far *ip;          /*удаленная -> в индекс экрана*/
        SCR              far *sp;      /*удаленная -> в дескриптор экрана*/

        BIOS_VIDEO_DATA far *vp = (BIOS_VIDEO_DATA far *) 0x00400049L;
                                       /*удаленная -> в область видео дан-
                                                              ных BIOS*/
        MOVE_XCHG mcb;                 /*структура передвижения/обмена
                                                         областей*/

        #pragma check_stack(off)
                                           /*очищается, если "control-c"*/
        void break_handler(sig_type)
        int sig_type;
        {
             signal(SIGINT, SIG_IGN);      /*запрещает "control-c" во время
                                                              драйвера*/
             cleanup;
             exit(0);
        }

             /*
                   драйвер прерывания для прерывания печати экрана
                   Делает моментальный снимок обычной памяти в
                   pасшиpенную память
             */
        void interrupt cdecl far int5_isr(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,
                                                          cs,flags)
        unsigned  es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs,flags;
        {
             static int rc = 0;             /*отслеживается последний код
                                                               возврата*/
             int status;
             if (!isr_status)               /*если обслуживание прерывания
                                            не активизировано, подцепить к
                                          предыдущему драйверу прерывания*/

                                      - 7-78 -
                _chain_intr(old_int5);
             if (rc == 0)  {            /*продолжать только, если ранее не
                                                          было ошибок*/
                rc = EMSGetPageMap(emm_save);

                if (rc == 0)  {               /*контекст сохранен успешно*/

                                                  /*сделать снимок экрана*/
                   rc = dump_screen();
                          /*восстановить контекст предыдущего отображения*/

                   if (status = EMSSetPageMap(emm_save))
                      rc = status;                  /*обновить код ошибки*/
                }
             }
             /*если произошла какая-либо ошибка, объявить вслух*/

             if (rc)
                Beep(32000);
        }

        dump_screen()
        {
        int rc;
        unsigned int overflow, new_offset, scr_size;

                 /*отобразить индексные данные экрана в логической странице
                            0 в физическую страницу 0*/

             if (rc = EMSMapHandlePage(emm_handle, 0, 0))
                return(rc);                                       /*отказ*/

             /*убедитесь, что не вышли из индексных элементов*/

             if (ip->scr_count >= ip->scr_max)
                return(1);                         /*переполнение индекса*/

             /*если экран переполняет страницу, разместите одну или более
                                      дополнительных страниц*/

             scr_size = vp->crt_cols * SCR_ROWS * 2;      /*кол-во байтов в
                                                                   экране*/
             new_offset = sp->scr_offset + scr_size;      /*новое смещение
                                                      в буфере сохранения*/

             if (new_offset  > PAGE_SIZE) {  /*экран переполняет страницу*/

                overflow = new_offset - PAGE_SIZE;   /*величина переполне-
                                                             ния страницы*/
                emm_pages += (overflow / PAGE_SIZE);  /*кол-во необходимых
                                                   дополнительных страниц*/
                new_offset = overflow % PAGE_SIZE;   /*размер добавочного
                                                                фрагмента*/
                if (new_offset)          /*прибавить страницу на добавок,*/
                   emm_pages++;                         /*если необходимо*/

                if (rc = EMSReallocPages(emm_handle, &emm_pages))

                                      - 7-79 -
                   return(rc);                                   /*отказ*/
             }

             /*снимок, отображающий видеоэкран в расширенную память*/

             mcb.region_length = (long) scr_size;  /*кол-во байтов на экра-
             mcb.source.memory_type =  CONV_MEM;  не в обычной памяти*/
             mcb.source.handle      = 0;  /*никакой обpаботчик не исп-ся*/
             mcb.source.initial_offset = vp->crt_start;  /*начальное сме-
                                                         щение экрана*/
             mcb.source.initial_seg_page = (vp->crt_mode == 7) ? MDA_SEG :
                                                                   CGA_SEG;
                  /*работает только с цветным/монохромным текстом*/
             mcb.dest.memory_type        = EXP_MEM;       /*идет в расши-
                                                          ренную память*/
             mcb.dest.handle             = emm_handle;    /*предварительно
                                                 размещенный обpаботчик*/
             mcb.dest.initial_offset = sp->scr_offset; /*следующее до-
                                                     ступное смещение*/
             mcb.dest.initial_seg_page = sp->scr_page;  /*в текущей стра-
                                                                    нице*/

             if (rc = EMSMoveRegion(&mcb))  /*функция "Передвинуть область
                                 памяти" спецификации pасшиpенной памяти*/
                return(rc);                                     /*отказ*/

             Beep(1000);       /*выдать короткий звуковой сигнал - успех*/

             /*обновить индексные данные экрана ("Передвинуть область" не
                               нарушает контекста отображения*/

             ip->scr_count++;  /*уменьшение кол-ва сохраненных экранов*/
             sp->scr_len = src_size; /*запомнить кол-во байтов на экране*/
             sp->scr_width = vp->crt_cols;/*запомнить кол-во колонок в
                                            строке*/
             sp++;               /*указать на следующий индексный элемент*/
             sp->scr_len = 0;       /*новый еще не снятый экран,*/
             sp->scr_width = 0;      /*поэтому нулевые длина и ширина*/
             sp->scr_page = emm_pages -1;     /*новый экран идет на послед-
                                               нюю  размещенную страницу,*/
             sp->scr_offset = new_offset;     /*непосредственно следуя за
                                                       предыдущим экраном*/
             return(rc);                                       /*успех*/
        }

             /*освободить расширенную память, если размещена*/

        cleanup()
        {
             int rc;
             /*игнорировать код возврата, так как вызов может быть из
                             ошибочной процедуры*/
             if (emm_handle != -1)
                rc = EMSDeallocatePages(emm_handle);
        }

        #pragma check_stack(on)

                                      - 7-80 -

        main(argc, argv)
        int argc;
        char *argv[];
        {
             int emm_present, rc;
             unsigned int far *pseg_top;
             char emm_ver[4];

             get_opts(argc, argv);     /*получить переключатели командной
                                                                строки*/
             emm_handle = -1;          /*нет размещенных обpаботчиков
                                             pасшиpенной памяти*/
             /*установить драйвер "Control-C" (прерывание)*/

             signal(SIGINT, break_handler);

             /*проверка наличия pасшиpенной памяти*/

             if (rc = emm_exists(emm_present))   {     /*тест наличия
                                   менеджеpа pасшиpенной памяти не прошел*/
                printf("snapshot: EMM presence test failed, rc: %d", rc);
                exit(2);
             }

             if (!emm_present)   {   /*нет pасшиpенной памяти*/
                printf("snapshot: No expanded memory is present");
                exit(1);
             }

             /*получить версию спецификации pасшиpенной памяти, которую
                     поддерживает менеджеp pасшиpенной памяти*/

             if (rc = EMSGetVersion(emm_ver))   {
                DisplayError(rc);
                exit(1);
             }

             /*убедитесь, что это - по крайней мере, версия 4.0*/

             if (*emm_ver < '4') {    /*требуется спецификация pасшиpенной
                                                  памяти LIM 4.0 или выше*/
                printf("snapshot: Unsupported EMM version detected: %s,
                   LIM EMS 4.0 or greater is required", emm_ver);
                exit(1);
             }

             /*получить указатель на кадр страниц спецификации pасшиpенной
                               памяти*/

             if (rc = EMSGetFrameAddr(&page_frame))   {
                DisplayError(rc);
                exit(1);
             }

             /*поиск обpаботчика спецификации pасшиpенной памяти, который
                          содержит запомненные экраны*/

                                      - 7-81 -

             rc = EMSSearchHandleName(HANDLE_NAME, &emm_handle);

             /*ошибка, если любой код возврата, отличный от 'нормально'
                        или 'обpаботчик не найден'*/

             if (rc != 0 && rc != HNDVLNFD)   {
                DisplayError(rc);
                exit(1);
             }

             /*или закончить и остаться резидентно, установить программу,
             остающуюся после завершения резидентно, или показать ее текущее
                                состояние*/

             if (terminate_flag)  {    /*пользователь запросил завершение*/

                if (rc == 0)      /*обpаботчик с нашим именем существует,*/
                     terminate();        /*поэтому постараться снять себя*/
                else  {          /*обpаботчик не существует, поэтому не мо-
                                                 жет завершиться */
                printf("snapshot: can't terminate - not installed");
                exit(1);
                }

             } else  {           /*или установиться или выдать состояние*/
                /* если обработчик, именованный HANDLE_NAME, уже сущест-*/
                /*вует, только сообщить,  сколько памяти спецификации   */
                /*расшиpенной памяти в настоящий  момент размещено, и как*/
                /*много экранов хранится в ней. В противном случае, уста-*/
                /*новить программу обслуживания прерывания для прерывания*/
                /*печати и сделать данную программу резидентной*/

                   if (rc == 0) /*обpаботчик с нашим именем уже сущест-*/
                      show_status(); /*вует,поэтому только показать состоя-
                                                                      ние*/
                   else  {          /*обpаботчик не существует,*/
                      install();    /*поэтому разместить обpаботчик и уста-
                                   новить программу обработки прерывания*/

                           /*завершиться и остаться резидентно*/

                      FP_SEG(pseg_top) = _psp; /*конечный параграф програм-
                                                мы находится по psp+2 */
                      FP_OFF(pseg_top) = 2;

                      printf("snapshot: TSR installing at segment [%04X],
                       size %u paragraphs\n", _psp, *pseg_top  - _psp);

                      _dos_keep(0, *pseg_top - _psp);  /*кол-во параграфов
                                                            в программе*/
                   }
             }
        }

             /*отобразить идентификатор обpаботчика, кол-во логических*/
             /*страниц, размещенных для него, и кол-во экранов, запомнен-*/

                                      - 7-82 -
             /*ных в данный момент в pасшиpенной памяти*/

        show_status()
        {
             int rc;
             unsigned int alloc_pages, screens;

             /*определение кол-ва страниц, размещенных для данного обpа-*/
                       /*ботчика менеджеpа pасшиpенной памяти*/

             if (rc = EMSGetHandlePages(emm_handle, &alloc_pages))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }

             /*отобразить первую логическую страницу, содержащую индекс
               экрана, в нулевую физическую страницу*/

             if (rc = EMSMapHandlePage(emm_handle, 0, 0))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }

             /*получить адресуемость для структуры данных индексов экранов
                в pасшиpенной памяти*/

             ip = (SCR_INDEX far *) page_frame;

             /*печатать текущее состояние*/

                  printf("snapshot: status - EMS handle (%d); EMS
                     pages (%d); screens (%d)\n", emm_handle, alloc_pages,
                         ip->scr_count);

             /*перестать отображать страницу индексов экрана*/

             if (rc = EMSMapHandlePage(emm_handle, -1, 0))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }
        }

             /*Получить обpаботчик спецификации pасшиpенной памяти с од-*/
             /*ной страницей от менеджеpа pасшиpенной памяти и дать ему*/
             /*наименование, так чтобы другие программы могли обращаться*/
             /*к нему. Инициализировать структуру индексных данных экра-*/
             /*нов, которая будет размещена в начале первой страницы спе-*/
             /*цификации pасшиpенной памяти. Затем  подставьте программу*/
             /*обслуживания прерывания для прерывания печати экрана, так*/
             /*чтобы образы экранов сохранялись в pасшиpенной памяти, */
             /*когда пользователь нажимает клавишу PrtScr.*/

        install()


                                      - 7-83 -
        {
             int rc, context_bytes;

             /*разместить одну страницу для начала*/

             emm_pages = 1;

             if (rc = EMSAllocatePages(&emm_handle, emm_pages))   {
                DisplayError(rc);
                exit(1);
             }

             /*дать обpаботчику имя, так чтобы другие программы могли */
                                   /*  найти его */
             if (rc = EMSSeyHandleName(emm_handle, HANDLE_NAME))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }
             printf("snapshot: allocated expanded memory handle # %d with
                     name '%s'\n", emm_handle, HANDLE_NAME);

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

             if (rc = EMSMapHandlePage(emm_handle, 0, 0))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }

             /*получить адресуемость для структуры данных индексов экранов
                в pасшиpенной памяти*/

             ip = (SCR_INDEX far *) page_frame;

             /* инициализировать данные в ней */

             ip->scr_count = 0;           /*кол-во  сохраненных экранов*/
             ip->scr_max = MAX_SCR;       /*максимальное кол-во  сохранен-
                                                              ных экранов*/
             ip->scr_int5 = int5_isr;     /*указатель на нашу программу
                                    обслуживания прерывания печати экрана*/
             sp = ip->scr_idx;             /* -> первый индексный элемент*/
             sp->scr_page = sizeof(*ip) / PAGE_SIZE;  /*экраны начинаются*/
             sp->scr_offset = sizeof(*ip) % PAGE_SIZE;
                                                      /*сразу за индексом*/
             sp->scr_len = 0;                         /*вначале пусто*/
             sp->scr_width = 0;

             /*перестать отображать страницу индексов экрана*/

             if (rc = EMSMapHandlePage(emm_handle, -1, 0))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }

                                      - 7-84 -

             /*разместить область сохранения контекста pасшиpенной памяти,
               используемую драйвером прерывания печати экрана*/

             if (rc = EMSGetPageMapSize(&context_bytes))  {
                DisplayError(rc);
                cleanup();
                exit(1);
             }

             if ((emm_save = (PMAP *) malloc(context_bytes)) == NULL)   {
                printf("snapshot: Couldn't allocate %d bytes for context
                     save area", context_bytes);
                cleanup();
                exit(1);
             }

             /*установить обpаботчик прерывания для перехватывания запросов
                                    печати экрана*/

             old_int5 = _dos_getvect(PRTSC_INT);   /*сохранить старый век-
                                                     тор прерывания*/
             _dos_setvect(PRTSC_INT, int5_isr);    /*установить новый век-
                                                     тор прерывания*/
        printf("snapshot: print screen interrupt handler is installed\n");

        isr_status = 1;
        /* пусть новый драйвер обслуживает прерывания*/
        printf("snapshot: print screen interrupt handler is activated\n");
             }

             /* снять программу, резидентную после завершения, из памяти
                       по запросу пользователя */

        terminate()
        {
            int rc;
            unsigned int tsr_psp;         /*префиксный программный сегмент
                            активной программы, резидентной по завершении*/

            unsigned int far *envptr;     /*указатель среды программы, ре-
                                             зидентной по завершении*/

            void (interrupt far *our_int5)(); /*адрес установленной прог-
                                        раммы, резидентной по завершении*/

             /* приостановить обработку прерываний печати экрана */

             isr_status = 0;
             printf("snapshot: print screen interrupt handler
                        deactivated\n");

             /* отображение в страницу, содержащую индекс экрана */

             if (rc = EMSMapHandlePage(emm_handle, 0, 0))  {
                DisplayError(rc);
                cleanup();

                                      - 7-85 -
                exit(1);
             }

             /*получить адресуемость для структуры данных индексов экра-*/
             /*нов в pасшиpенной памяти, так чтобы мы  могли получить ад-*/
             /*рес программы обслуживания прерывания, которую мы установ-*/
             /*вили, когда данная программа стартовала*/

             ip = (SCR_INDEX far *) page_frame;
             our_int5 = ip->scr_int5;          /*получить запомненный адрес
                                        программы обслуживания прерывания*/

             /* освободить pасшиpенную память */

             cleanup();
             printf("snapshot: expanded memory handle %d deallocated\n",
                        emm_handle);

             /* если никакой другой драйвер печати экрана не был установ-*/
             /*лен перед нами, отключить программу обслуживания прерыва-*/
             /*ния и вновь установить программу*/

             if (_dos_getvect(PRTSC_INT) == our_int5)  {  /*наша программа
                                         обслуживания прерывания - первая*/

                /*восстановить старый вектор прерывания печати экрана */

                _dos_setvect(PRTSC_INT, old_int5);
                printf("snapshot: old print screen interrupt handler
                           restored\n");

                /* освободить строки среды программы, резидентной по завер-
                   шении, и программный сегмент*/

                tsr_psr = FP_SEG(our_int5) - 16;  /*PSP начинается за 1666
                                              параграфов до сегмента кода*/
                printf("snapshot: deallocating TSR at segment [%04X]\n",
                           tsr_psp);

                FP_SEG(envptr) = tsr_psp;    /*указатель среды - по смеще-
                                                                 нию*/
                FP_OFF(envptr) = 0x2C;       /* 2Ch в префикс сегмента про-
                                                              граммы*/
                _dos_freemem(*envptr);       /*освободить строки среды*/
                _dos_freemem(tsr_psp);       /*освободить программный сег-
                                                          мент*/

             } else  /*наша программа обслуживания прерывания не является
                       первой в цепи, нельзя снимать программу, резидентную
                                после завершения*/
                printf("snapshot: cannot deallocate TSR - print screen ISR
                           is not first in chain\n");
        }
             /*процесс командной строки переключается в форму /L,  где */
             /*'L' - идентификация переключателя по одному символу. Воз-*/
             /*вращается индекс первого элемента в массиве указателей, */
             /*следующем за переключателями*/

                                      - 7-86 -

        get_opts(cnt, ptr)
        int cnt;
        char *ptr;
        {
             int argc;

             terminate_flag = 0;          /*сбросить флаг завершения*/

             arc = 1;
             while (*++ptr)[0] == '/'  {
                switch(*ptr)[1])  {

                   case '?'               /*команда отображения и исполь-
                                            зование переключателей*/
                      printf("snapshot: saves text screen images to
                                expanded memory\n");
                      printf("usage: snapshot [/X]\n");
                      printf("   /X - terminates snapshot");
                      exit(0);
                      break;

                   case 'x':                 /*завершает запрос*/
                   case 'X':

                      terminate_flag = 1;
                      break;
                   default:                  /*неизвестный переключатель*/
                      printf("'%c' is an unknown option\n", (*ptr)[1]));
                      break;
                }
                argc++;
             }
             return(argc);
        }
        -------------------------------------------------------------------







                             Листинг 7-9. PLAYBACK.C
        -------------------------------------------------------------------
            /*
                 Имя: PLAYBACK.C
                 Назначение: Распечатка образов текстовых экранов, сохра-
                 ненных в pасшиpенной памяти программой SNAPSHOT, в стан-
                 дартный выходной файл DOS.
            */

        #include <stdio.h>
        #include <stdlib.h>
        #include <signal.h>
        #include <dos.h>
        #include <bios.h>

                                      - 7-87 -

        #include "emmconst.h"    /*константы менеджеpа pасшиpенной памяти*/
        #include "emmtypes.h"    /*структуры данных менеджеpа pасшиpенной
                                                                 памяти*/
        #include "emmerr.h"    /*коды ошибок менеджеpа pасшиpенной памяти*/
        #include "emmfunc.h"     /*объявления функций менеджеpа pасшиpенной
                                                                 памяти*/
        #define DisplayError(rc)  ShowEMMErr(rc, __LINE__, __FILE__)

        #define HANDLE_NAME       "SNAPSHOT"  /*имя обpаботчика pасшиpенной
                                                                  памяти/*
        #define MAX_SCR           500         /*максимальное кол-во сохра-
                                                        няемых экранов*/
        #define SCR_COLS          80          /*предполагается 80 колонок,
                                               позже можно корректировать*/
        #pragma pack(1)                       /*структуры данных с байтовым
                                                         выравниванием*/

        typedef struct scr {              /*дескриптор данных экрана*/
             unsigned int   scr_page;     /*страница pасшиpенной памяти для
                                                        начального экрана*/
             unsigned int   scr_offset;   /*смещение pасшиpенной памяти для
                                                        начального экрана*/
             unsigned int   scr_width;    /*кол-во колонок на экране*/

             unsigned int   scr_len;      /*длина экрана в байтах*/
        } SCR;

        typedef struct scr_index {          /*индексная структура экранов*/
             void (interrupt far *scr_int5)(); /*указатель на нашу програм-
                                               му обслуживания прерывания*/
             unsigned int   scr_count;         /*текущее кол-во сохраненных
                                                                экранов*/
             unsigned int   scr_max;           /*максимальное кол-во сохра-
                                                    ненных экранов*/
             SCR            scr_idx[MAX_SCR];  /*массив индексов экранов*/
        } SCR_INDEX;

             /*
                     Глобальные данные
             */

        unsigned int  emm_handle,    /*обpаботчик pасшиpенной памяти*/
                        emm_pages;   /*кол-во страниц, принадлежащих
                                                      обpаботчику*/
        char far       *page_frame;  /*удаленный -> на кадр страниц
                                   спецификации pасшиpенной памяти*/
        SCR_INDEX      far *ip;      /*удаленный -> на индекс экрана*/
        SCR            far *sp;      /*удаленный -> на дескриптор эк-
                                                             рана*/

        MOVE_XCHG mcb;     /*структура "Передвинуть/обменять области*/

        main()
        {
             unsigned int   scan_code;
             int emm_present, rc, current_screen;

                                      - 7-88 -
             char emm_ver[4];

             /*тест наличия pасшиpенной памяти*/

             if (rc = emm_exists(&emm_present))   {
                    /*тест наличия менеджеpа pасшиpенной памяти не прошел*/
                 printf("replay: EMM presence test failed, rc: %d", rc);
                 exit(2);
             }

             if (!emm_present)   {
                    /* pасшиpенной памяти нет */
                 printf("replay: No expanded memory is present");
                 exit(1);
             }

             /*получить версию спецификации pасшиpенной памяти,поддерживае-
                 мую данным менеджеpом pасшиpенной памяти*/

             if (rc = EMSGetVersion(emm_ver))   {
                DisplayError(rc);
                 exit(1);
             }

             /* убедитесь, что версия не ниже 4.0 */

             if (*emm_ver < '4')  {    /*требуется спецификация pасшиpенной
                                       памяти LIM не ниже 4.0*/
                 printf("replay: Unsupported EMM version detected: %s,
                    LIM EMS 4.0 or greater is required", emm_ver);
                 exit(1);
             }

             /* получить указатель на кадр страниц спецификации  pасшиpен-
                           ной памяти */

             if (rc = EMSGetFrameAddr(&page_frame))   {
                DisplayError(rc);
                 exit(1);
             }
             /* Поиск обpаботчика, содержащего запомненные экраны */

             if (rc = EMSSearchHandleName(HANDLE_NAME, &emm_handle))   {
                DisplayError(rc);
                 exit(1);
             }

             /* отобразить в страницу, содержащую индекс экрана */

             if (rc = EMSMapHandlePage(emm_handle, 0, 0))   {
                DisplayError(rc);
                 exit(1);
             }

             /* получить адресуемость на структуру данных индексов
                      экранов */


                                      - 7-89 -
             ip = (SCR_INDEX far *) page_frame;
             sp = ip->scr_idx;      /*указать на первый запомненный экран*/

             if (ip->scr_count == 0)
                printf("replay: no screens have been saved");
             else
                /*
                   распечатывать каждый запомненный экран на стандартный
                   вывод
                */
             for (current_screen = 0; current_screen < ip->scr_count;
                  current_screen++)  {
                rc = print_screen(sp++);

             if (rc)  {             /*произошла ошибка pасшиpенной памяти*/
                   DisplayError(rc);
                    exit(1);
                }
             }

             /* перестать отображать страницу индексов экранов */

             if (rc = EMSMapHandlePage(emm_handle, -1, 0))   {
                DisplayError(rc);
                 exit(1);
             }
        }

             /*
                 По данному удаленному указателю на дескриптор экрана, за-
                 помненный в pасшиpенной памяти, пишите каждый символ за-
                 помненного образа экрана в стандартный выходной файл DOS
             */

        print_screen(sp)
        SCR far *sp;               /*удаленный -> на дескриптор экрана*/
        {
             int rc, lpages, line, rows;
             char *line_buf[SCR_COLS+1];
             int far *bp;
             struct SREGS segregs;

             /* вычислить, сколько физических страниц нужно отобразить */

             lpages = 1;           /* по крайней мере, одна страница */
             if (sp->scr_offset + sp->scr_len > PAGE_SIZE)
                  lpages++;

             /* отобразить логическую страницу (страницы), которые содержат
             образ экрана, в физические страницы, начинающиеся с физической
                                  страницы 1 */

             for (i = 0; i < lpages; i++)
                if (rc = EMSMapHandlePage(emm_handle, i + sp->scr_page,
                                              i + 1))
                   return(rc)                          /* неудача */


                                      - 7-90 -
             /* получить адресуемость на физическую страницу 1 */

             bp = (int far *) page_frame;          /* базовый адрес кадра
                                                                 страниц*/
             FP_SEG(bp) += (PAGE_SIZE / 16);       /*кол-во параграфов в
                                 странице спецификации pасшиpенной памяти*/
             FP_OFF(bp) = sp->scr_offset;

             rows = sp->scr_len / sp->scr_width / 2;  /*вычисление кол-ва
                                                          строк на экране*/
             putchar('[bs]014');                  /*начать новую страницу*/

             /* записывать каждый символ на образе экрана в стандартный
                                       выход */

             for (line = 0; line < rows; line++) {
                i = sp->scr_width;
                while (i--)
                   putchar(*bp++ & 0xFF);
                putchar('[bs]n');           /* вывод новой строки после
                                                    каждой строки */
             }
             return(rc);
        }
        -------------------------------------------------------------------


                              Листинг 7-10. BEEP.ASM
        -------------------------------------------------------------------
             TITLE   Beep
        _TEXT           SEGMENT         BYTE PUBLIC 'CODE'
        _TEXT           ENDS
        _DATA           SEGMENT  WORD PUBLIC 'DATA'
        _DATA           ENDS
        CONST    SEGMENT          WORD PUBLIC 'CONST'
        CONST    ENDS
        _BSS            SEGMENT  WORD PUBLIC 'BSS'
        _BSS            ENDS
        DGROUP  GROUP    CONST, _BSS,       _DATA
                ASSUME   CS:_TEXT, DS:DGROUP, SS:DGROUP, ES:DGROUP
        _TEXT           SEGMENT
        timer   equ     40h
        port_b          equ      61h
        ;-----  Издает слышимый звук на внутреннем громкоговорителе ПЭВМ
        ;       Длительность звука управляется одним целым аргументом.
        ;
                PUBLIC  _Beep
                PUBLIC  _Beep
        _Beep   PROC    NEAR
                push    bp
                mov     bp,sp
                mov     al,10110110B   ;генерируется короткий звук (длинный
        ;                               теряет данные)
                out     timer+3,al     ;код, взятый из технич-го описания
                mov     ax,533H
                out     timer+2,al
                mov     al,ah

                                      - 7-91 -
                out     timer+2,al
                in      al,port_b
                mov     ah,al
                or      al,03
                out     port_b,al
                mov     cx,[bp+4]
                mov     bl,1
        beep0:  loop    beep0
                dec     bl
                jnz     beep0
                mov     al,ah
                out     port_b,al
                pop     bp
                ret
        _Beep           ENDP
        _TEXT           ENDS
                END
        -------------------------------------------------------------------





                 Глава 8. ПРОГРАММИРОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА

                 Основы асинхронной последовательной связи
                 Последовательный порт с точки зрения программиста
                 Использование  средств  MS-DOS для программирования
                 последовательного порта
                 Пример программы
                 Заключение


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


                     Основы асинхронной последовательной связи

             Говоря о передаче данных,  мы интересуемся  передачей  байтов
         данных от одного устройства к другому, например, от персонального
         компьютера к модему или к  последовательному  принтеру.  Если  мы
         имеем  восемь линий между двумя устройствами,  то мы можем назна-
         чить каждой линии бит и послать сразу один байт данных. Это будет
         параллельная  передача.  Таким образом работает параллельный порт
         персонального компьютера, кроме того, в дополнение к восьми лини-
         ям  данных имеются другие сигнальные линии,  оказывающие помощь в
         передаче данных.
             С другой стороны,  если мы имеем одну линию для передачи сиг-
         налов, то необходимо посылать каждый байт данных последовательно,
         по одному биту.  Более того,  мы может посылать данные синхронно,
         таким образом,  что каждый байт посылается в  ранее  определенное
         время (скажем, один байт каждые х секунд), или асинхронно со ско-
         ростью, которую предварительно определять необязательно.
             Последовательная связь  дешевле,  чем  параллельная,  так как
         требует меньше линий передачи данных - минимум две для двусторон-
         ней связи. Кроме того режим асинхронной передачи оказывает значи-
         тельно меньшее воздействие на аппаратуру ввиду того,  что не тре-
         буется  дополнительное  специальное  оборудование  для  поддержки

                                      - 8-2 -
         синхронизации между передатчиком и приемником.
             Таким образом,  асинхронная  последовательная  связь является
         предпочтительным решением ввиду низкой стоимости и  простоты  ис-
         пользуемых аппаратных средств. Конечно, в этом режиме передачи мы
         должны преобразовывать каждый байт данных в серию битов и  указы-
         вать приемнику начало и конец каждого байта.  На рисунке 8-1 про-
         иллюстрирована концепция асинхронной последовательной связи.
             Предположим, что мы умеем преобразовывать каждый байт в поток
         единиц и нулей,  то есть биты,  которые могут быть переданы через
         среду связи (например,  телефонную линию).  В самом деле, универ-
         сальный асинхронный приемопередатчик (UART), как мы увидим в сле-
         дующем разделе,  выполняет точно такую же функцию.  Обычно,  в то
         время как линия находится в режиме ожидания, для демонстрации то-
         го, что линия в порядке, по ней передается единица, обозначая не-
         занятость линии. С другой стороны, когда линия находится в состо-

    Ъ Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д  ¬
                                    Восстанавливается один байт
    ¦                               А  7  6  5  4  3  2  2  1 ЩАДДДД¬
      ЪДДДПринимается один символДДДД¬АДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩ ЪДДД¬¦
    ¦  ¦A¦¦B¦¦7¦¦6¦¦5¦¦4¦¦3¦¦1¦¦0¦¦C¦                      ¦¦
   --> АДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩ ЪДДДДДВДДДДДДДДДВДДДДД¬  ¦ ЪДЩАД¬
   ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  D  ¦         ¦     ¦    ¦    ¦
   ¦¦    от модема                    АДДДДДБДДДДДДДДДБДДДДДЩ  ¦ ГДДДДґ
   ¦  UART на приемном конце             скорость в бодах        ¦    ¦
   ¦А Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д ДЩ ¦    ¦
   ¦                                                             ГДДДДґ
   ¦  ЪДДДДДДД¬                                                  ¦    ¦
   АДДґ       ГДД¬                                               АДДДДЩ
      АДДДДДДДЩ  ¦                                           Принимающий
                 ¦        Телефонная линия                    компьютер
                Ъ†¬___________________________ЪД¬
                АДЩ      <--                  А†Щ
                                               ¦
                                            6  ¦
                                               ¦
                                               ¦       ЪДДДДДДД¬
                                               АДДДДДДДґ модем ГДДДДДДДД¬
         ЪДДДДДДДД¬                                    АДДДДДДДЩ        ¦
         ¦        ¦                                                     ¦
         ¦   PC   ¦                                                     ¦
      ЪДДБДДДДДДДДБДД¬                                                  ¦
      ¦              ¦                                                  ¦
      А¬ЪДДДДДДДДДДДДЩ                                                  ¦
       ¦¦  Ъ Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д  ¬ ¦
      ¦¦  ¦ ЪДДДДДДДД БайтДДДДДДДДДДД¬                                 ¦
       ¦¦      7  6  5  4  3  2  2  1                                 ¦ ¦
       ¦АДДЩ  АДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩЪДДДПосылаемый  один символДДДД¬  ¦
       АДДД¬                   ¦A¦¦B¦¦7¦¦6¦¦5¦¦4¦¦3¦¦1¦¦0¦¦C¦ ¦ ¦
              ЪДДДДДВДДДДДДДДДВДДДДД¬  АДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩАДЩ   ¦
           ¦  ¦     ¦         ¦  D  ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
              АДДДДДБДДДДДДДДДБДДДДДЩ         к модему -->            ¦
           ¦  скорость в бодах     UART в последовательном адаптере PC
           А Д Д Д Д Д Д Д Д Д Д ДД Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Щ

                   Рис.8-1. Асинхронная последовательная связь.
                 A-стоп; B-четность; C-начало; D-сдвиговый регистр

                                      - 8-3 -
         янии   логического  нуля,  говорится,  что  она  стоит  в  режиме
         выдерживания интервалов. Таким образом, логические единица и ноль
         рассматриваются, соответственно, как MARK и SPACE.
             В асинхронной  связи изменение условия состояния линии с MARK
         на SPACE означает начало символа (смотри рисунок 8-2).  Это назы-
         вается стартовым битом. За стартовым битом следует комбинация би-
         тов,  представляющая символ, и затем бит контроля четности. Нако-
         нец,   линия   переходит   в  состояние  ожидания  MARK,  которая
         представляет собой стоповый бит и означает конец текущего  симво-
         ла.  Число битов, используемых для представления символа, называ-
         ется длиной слова и обычно бывает равно семи  или  восьми.  Конт-
         рольный  бит используется для выполнения элементарной проверки на
         наличие ошибки.

                  <---------     Направление передачи
      Линия                                     Линия возвращается в
      свободна                                   свободное состояние
             A                                          
      B ДДДД¬ Ъ Д В Д В Д В Д В Д В Д В Д В Д В Д ВДДДВДДДДДДД¬  Ъ Д Д
            ¦  ¦ 0   1   2   3   4   5   6   7     ¦           ¦
      C Д Д АДДЩ Д Б Д Б Д Б Д Б Д Б Д Б Д Б Д Б Д Б Д Б Д Д Д АДДЩ Д Д
              АДДДДДДДД 7 или 8 бит данных ДДДЩ               
         Стартовый                       бит четности¦    Начало другого
           бит                                       ¦      символа
                                              бит стоповый
                      Время ----->

              Рис.8-2. Представление в асинхронной  последовательной
                         связи формата одиночного символа
                A-длительность 1 бита; B-MARK или 1; C-SPACE или 0

             Как передатчик  (или  приемник) узнают о длительности каждого
         бита?  Действительно,  и передатчик,  и приемник должны знать его
         длительность или детектирование битов будет невозможно.  Длитель-
         ность каждого бита определяется генераторами  тактовых  импульсов
         приемника и передатчика. Отметим, однако, что генераторы в прием-
         нике и передатчике должны иметь одну и ту же частоту,  но не тре-
         буется, чтобы они были синхронизированы. Выбор частоты генератора
         зависит от скорости передачи в бодах,  которая означает число из-
         менений состояния линии каждую секунду. Номинально  тактовая час-
         тота "16-кратная скорость передачи в бодах" означает,  что  линия
         проверяется  достаточно часто для надежного распознавания старто-
         вого бита.
             Существует одно  обычное состояние линии,  которое иногда ис-
         пользуется для привлечения внимания приемника. Нормальным состоя-
         нием  линии  является  MARK (или 1) и начало символа определяется
         переходом SPACE (0). Если линия находится в состоянии SPACE в те-
         чение периода времени большем,  чем время,  которое она затратила
         бы на получение всех битов символа,  тогда мы говорим, что насту-
         пило  состояние  BREAK.  В  кодах ASCII отсутствует представление
         BREAK - это означает,  что линия  "умерла"  на  непродолжительный
         промежуток времени, который составляет BREAK.



                                      - 8-4 -
                     Контроль по четности и обнаружение ошибок

             Ранее мы упоминали, что бит контроля четности полезен для об-
         наружения  ошибок.  Например,  если выбрана проверка на четность,
         этот бит устанавливается таким образом,  что общее число единиц в
         текущем  слове  является четным (такая же логика используется для
         проверки на нечетность).  В приемнике четность вычисляется заново
         и сравнивается с битом контроля четности.  Если они не равны,  то
         приемник сообщает,  что имеет место ошибка четности.  Главный не-
         достаток обнаружения ошибки посредством проверки на четность зак-
         лючается в том,  что можно только обнаружить ошибки, которые вли-
         яют на один единственный бит.  Например,  битовая комбинация 0100
         0001 0 (ASCII A),  переданная восемью битами с проверкой на  чет-
         ность, может измениться (скажем,из-за шума в линии) на 0100 01110
         (ASCII G), однако приемник  не  обнаружит ошибку, так как провер-
         ка на четность выполняется.

                     Связь с использованием стандарта RS-232C.

             Ранее мы  упоминали  о  передаче по телефонной линии единиц и
         нулей.   Несмотря на то,  что в персональном компьютере мы  пред-
         ставляем единицы и нули посредством уровней напряжения,  сигналы,
         передаваемые по телефонной линии обычно являются тонами различной
         частоты.  Устройство,  которое находится между аппаратурой персо-
         нального компьютера и передающей линией и делает возможной  пере-
         дачу данных,   называется модемом (модулятор/демодулятор).  Модем
         может преобразовывать информацию в  представление "напряжение/нет
         напряжения" цифровых схем и обратно,  а так же аналоговые сигналы
         (например,  тоны), предназначенные для передачи по телефонной ли-
         нии.  Стандарты, такие как RS-232C (выдвинутый Ассоциацией элект-
         ронной промышленности,  EIA),  описывают метод обмена информацией
         между  модемом  (или,  в  соответствии с терминологией ассоциации
         EIA,  "аппаратура передачи данных,  DCE") и  связной  аппаратурой
         персонального  компьютера  (или  "оконечная  аппаратура обработки
         данных,  DTE"). Модем может работать в двух режимах: полудуплекс-
         ном  и  дуплексном.  В полудуплексном режиме модем может осущест-
         влять передачу только в одном направлении в один промежуток  вре-
         мени,   в   то   время   как   при  работе  в  дуплексном  режиме
         осуществляется независимая двухсторонняя связь.  Стандарт RS-232C
         обеспечивает управление такими сигналами, как "запрос-на-передачу
         (RTS)" и "открыт-для -передачи (CTS)", которые могут быть исполь-
         зованы  для координации процесса передачи и приема данных. Термин
         "квитирование установления связи" используется для описания коор-
         динации приема и передачи сигналов.  Как показано на рисунке 8-3,
         стандарт RS-232C соответствует кабелю и соединителям,  используе-
         мым для связи персонального компьютера и модема.
             Несмотря  на то, что мы использовали модем в качестве примера
         аппаратуры передачи данных (DCE),  другие устройства,  такие  как
         "мышь" или принтер с соответствующей схемой,  также могут обмени-
         ваться данными с персональным компьютером  через последовательный
         порт.  Таким образом, в этой главе все упоминания о модеме прием-
         лемы в равной степени как к последовательному принтеру  так  и  к
         последовательной "мыши".

                                      - 8-5 -
              Ъ Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д ¬
               ОАОД   ЪДДДДД   Номер контакта  ДДДДДДДД¬   DCE
              ¦  ЪДДДДД¬                             ЪДДДДД¬  ¦
                 ¦    2ГДДДДДДДДДДДДДПДДДДДДДДДДДДДДДґ 2   ¦
              ¦  ¦    3ГДДДДДДДДДДДДДДПДДДДДДДДДДДДДДґ 3   ¦  ¦
                 ¦    4ГДДДДДДДДДДДДДЗПДДДДДДДДДДДДДДґ 4   ¦
              ¦  ¦    5ГДДДДДДДДДДДДОДПДДДДДДДДДДДДДДґ 5   ¦  ¦
                 ¦    6ГДДДДДДДДДДДДГПДДДДДДДДДДДДДДДґ 6   ¦         к
              ¦  ¦ ЪДД7ГДДДДДДДДДДДОбщийДДДДДДДДДДДДДґ 7ДД¬¦  ¦ телефонной
                 ¦   8ГДДДДДДДДДДДДСПЛДДДДДДДДДДДДДДґ 8  ¦       линии
              ¦  ¦   20ГДДДДДДДДДДДДТДГДДДДДДДДДДДДДДґ20   ¦  ¦        
                 ¦   22ГДДДДДДДДДДДДДRIДДДДДДДДДДДДДДґ22   ¦           ¦
              ¦  ¦    .¦                             ¦ .   ¦  ГДДДД¬   ¦
                 ¦    .¦                             ¦ .   ¦       ¦   ¦
            ЪДґ  ¦    .¦                             ¦ .   ¦  ¦    ¦   ¦
    RS ---> ¦    АДДДДДЩ                             АДДДДДЩ       ¦   ¦
            ¦ А Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Щ    ¦   ¦
        ЪДДДБДДДД¬                                                 ¦   ¦
        ¦        ¦                                                 ¦   ¦
        ¦        ¦                                                 ¦   ¦
     ЪДДБДДДДДДДДБДД¬                                            ЪДБДДДБД¬
     ¦              ¦                                            ¦       ¦
     АДДДДДДДДДДДДДДЩ                                            АДДДДДДДЩ
        PC или ОАОД                                          Модем или АПД
                              Рис.8-3. Связь RS-232C.
         ОАОД - оконечная аппаратура обработки данных; RS - кабель RS-232C;
         ПД - передача данных; П - прием данных; ЗП - запрос на передачу;
         ОДП - открыт для передачи; ГПД - готов к передаче данных;
         ДСПЛ - детектор сигнала приемной линии; ТДГ - терминал данных
         готов;  АПД - или аппаратура передачи данных


                       Управление потоком с помощью XON/XOFF

             В дополнение к квитированию  установления  связи  посредством
         аппаратных сигналов RTS/CTS,  для достижения управления потоком с
         использованием программного обеспечения  применяются  специальные
         управляющие  символы  ASCII  (Control-Q/Control-S или XON/ XOFF).
         Управлять потоком необходимо ввиду того, что иногда либо передат-
         чик  либо  приемник не могут поддерживать скорость передачи и они
         должны иметь возможность информировать другую сторону о необходи-
         мости  остановки  на время,  требуемое для того,  чтобы отставшая
         сторона смогла догнать другую.
             Предположим, что приемник имеет буфер для хранения  поступаю-
         щих символов. Как только буфер после заполнения закрывается, при-
         емник может послать символ XOFF  передатчику,  сигнализируя,  что
         передача должна быть приостановлена. Конечно, приемник должен по-
         нять значение XOFF и прекратить передачу символов.  Затем,  когда
         приемник обработает символы (скажем,  запишет их в файл на диске)
         и буфер освободится,  тогда посылается символ XON,  показывающий,
         что передача может быть продолжена.  Эта схема управления потоком
         широко применяется ввиду ее простоты.  Большинство связных  прог-
         рамм предоставляют возможность дуплексной связи с управлением по-
         током, основанном на применении символов XON/XOFF.



                                      - 8-6 -
                 Последовательный порт с точки зрения программиста

             Аппаратура последовательного порта в системах MS-DOS известна
         как последовательный адаптер или асинхронный связной адаптер (да-
         лее  мы  будем называть его последовательным адаптером).  Адаптер
         основан на Intel 8259 UART (универсальный асинхронный приемопере-
         датчик), имеет порт RS-232C для подключения к модему и, как адап-
         тер дисплея,  программируется посредством набора регистров.  Мик-
         ропроцессор  имеет  доступ  к  регистрам через ранее определенные
         адреса порта ввода/вывода.
             Универсальный асинхронный  приемопередатчик Intel 8250 управ-
         ляется посредством записи в набор восьмибитовых регистров и  чте-
         ния из них.  Эти регистры доступны программисту через адреса пор-
         та.  Адреса портов задаются последовательно,  поэтому  достаточно
         знать  адрес  первого порта.  Он также известен как базовый адрес
         последовательного адаптера. В персональном компьютере IBM PC двум
         последовательным портам COM1 и COM2 присвоены базовые адреса пор-
         та 3F8h и 2F8h соответственно. Так, для последовательного адапте-
         ра COM1 первый регистр имеет адрес 3F8h, следующий 3F9h и так да-
         лее.
             В 8250  имеется семь физических регистров и они описываются в
         порядке возрастания начального номера, начиная с базового адреса.
         Как показано на  рисунке 8-4,  базовый адрес порта имеет один ре-
         гистр,  который делится на два,  как приемный буферный регистр  и
         регистр хранения передачи (THR),  который используется для хране-
         ния одного передаваемого или принимаемого символа.  Затем следует
         регистр разрешения прерываний,  который используется для разреше-
         ния или блокировки генерации прерываний последовательным  адапте-
         ром.  Третий регистр, называемый регистром идентификации прерыва-
         ний,    содержит    сообщение     универсального     асинхронного
         приемопередатчика  об идентичности прерывания.  Затем следует ре-
         гистр управления линией,  используемый для установления различных
         связных  параметров,  таких как длина слова,  количество стоповых
         битов,  четность и скорость передачи в бодах. Пятый регистр - это
         регистр управления модемом, который используется для передачи мо-
         дему сигналов,  таких как DTR (терминал готов) и RTS  (запрос  на
         передачу). Наконец, два последних регистра, регистр состояния ли-
         нии и регистр состояния модема, показывают соответственно состоя-
         ние линии и модема.
             Первые два регистра применяются также для установки  скорости
         передачи  в  бодах.  Скорость  передачи  в бодах определяется как
         16-битовый делитель тактовой частоты, используемой для последова-
         тельного адаптера (1.8432 МГц в большинстве систем MS -DOS). Зна-
         чение делителя вычисляется по формуле

                                 1,843,200
             делитель= ------------------------------
                       16 Х скорость передачи в бодах

             Чтобы установить скорость передачи в бодах,  Вы должны проде-
         лать следующее:
             1. Установить в 1 наиболее значимый бит  регистра  управления
                линией  (он  называется битом защелки доступа делителя или
                DLAB).
             2. Загрузить младший и старший байты делителя соответственно в
                приемный буфер и регистр разрешения прерываний.
             3. Установить DLAB в 0 для обеспечения нормальной работы уни-
                версального асинхронного приемопередатчика.


                                      - 8-7 -
          A     Приемный буфер/регистр хранения передачи
          B     ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
       (COM1-3F8¦ 1.¦   ¦   ¦   ¦   ¦   ¦   ¦ 2.¦
       COM2-2F8)АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ

                Регистр разрешения прерывания
          B+1   ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
                ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 3.¦ 4.¦ 5.¦ 6.¦
                АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
                Установить бит в 1 для разрешения

                Регистр идентификации прерывания
          B+2   ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
                ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦   ¦   ¦ 7.¦
                АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
                                    АДД3-битовый идентификатор прерывания
                                     110 = состояние линии
                                     100 = приемные данные
                                     010 = буфер передачи свободен
                                     000 = состояние модема
                Регистр управления линией
          B+3   ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
                ¦ 8.¦   ¦    9.     ¦10.¦  11.  ¦
                АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
                      АДBREAK: 1 устанавливает линию в SPACE
                Регистр управления модемом
          B+4   ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬  a. - OUT2
                ¦ 0 ¦ 0 ¦ 0 ¦12.¦ a.¦ b.¦ c.¦ d.¦  b. - OUT1
                АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ  c. - RTS
                                  АДД 13.          d. - DTR
                Регистр состояния линии
          B+5   ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
                ¦ 0 ¦14.¦15.¦16.¦17.¦18.¦19.¦20.¦
                АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ

                Регистр состояния модема           a. - RLSD
          B+6   ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬  b. - Delta RLST
                ¦ a.¦ RI¦DSR¦CTS¦ b.¦ c.¦ d.¦ e.¦  c. - Delta RI
                АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ  d. - Delta DSR
             Рис.8-4. Регистры в 8250 UART.        e. - Delta CTS

             A - адрес порта или регистр
             B - базовый адрес; 1. - бит 7 данных;  2. - бит 0 данных;
             3. - состояние модема; 4. - состояние линии приема;
         5. - регистр хранения передачи свободен; 6. - прием данных
         разрешен; 7. - 0 означает, что прерывание ждет;
         8. - бит доступа к защелке; 9. - четность:  000=нет,
         001=нечетность, 011=четность; 10. - число стоповых битов;
         11. - длина слова: 10-7, 11-8; 12. - проверка обратного цикла;
         13. - должен быть 1 для  прерывания ввода/вывода персонального
         компьютера; 14. - передача свободна; 15. - регистр хранения пере-
         дачи свободен; 16. - обнаружен BREAK; 17. - ошибка кадровой
         синхронизации; 18. - ошибка четности;  19. - ошибка выхода за
         границы; 20. - данные для приема готовы;

             Применяя этот подход Вы можете установить любое значение ско-
         рости передачи в бодах.  Обратите внимание, что максимально  воз-

                                      - 8-8 -
         можной скоростью передачи является  1/16  тактовой  частоты,  или
         115,200  бод (для этой скорости передачи делитель равен 1).  Этот
         предел вытекает из того, что делитель не может быть меньше едини-
         цы.  Для  установки скорости передачи в бодах Вы можете также ис-
         пользовать прерывание BIOS 14h. Мы рассмотрим применение BIOS да-
         лее в этой главе.

               Управляемый прерываниями последовательный ввод/вывод

             Существует два общих метода ввода/вывода в любой вычислитель-
         ной системе:  упорядоченный и управляемый прерываниями.  Упорядо-
         ченность относится к повторяющейся  проверке  состояния  регистра
         устройства ввода/вывода для инициализации требуемой транзакции. В
         упорядоченном вводе/выводе программа, запрашивающая символ ввода,
         многократно считывает состояние регистра в устройстве ввода/выво-
         да до тех пор, пока оно не покажет, что символ доступен для ввода
         (или  до  тех пор,  пока программа не решит,  что "время закончи-
         лось"). Когда состояние указывает, что имеется готовый для работы
         символ, программа считывает его из соответствующего регистра уст-
         ройства ввода/вывода.  Сходная последовательность "ждать,  до тех
         пор пока не готов,  затем писать" используется при выведении сим-
         волов на устройство ввода/ вывода.  Таким образом, дальнейшее вы-
         полнение  программы  приостанавливается  до завершения выполнения
         операции ввода/вывода.
             Большой проблемой  для упорядоченного ввода/вывода через ком-
         муникационный порт является то,  что при скорости  передачи  выше
         300  бод  программе трудно что-либо сделать с получаемым символом
         кроме как отображать его на экране.  Рассмотрим следующий пример.
         Предположим,  что  мы читаем символы со скоростью 300 бод и имеем
         следующие связные параметры:  длина слова 7 бит, проверка на чет-
         ность и один стоповый бит, который вместе со стартовым битом, до-
         бавляет до 10 бит на символ. Вы ожидаете получать около 30 симво-
         лов  каждую секунду. После чтения  символа  программа имеет около
         1/30 секунды для выполнения других операций. Если  Вы не  желаете
         потерять какие-либо символы,то в это время Вы должны снова начать
         упорядочение порта.  Что произойдет,  когда скорость возрастет до
         9600 бод?  Временной интервал между символами слишком мал для вы-
         ведения символа на экран дисплея,  не позволяет  интерпретировать
         специальные символы и эмулировать терминал.
             В подходе,  основанном на управлении прерываниями,  программа
         предоставляет  возможность  прерываниям  устройства  ввода/вывода
         поступать непосредственно на центральный процессор,  который про-
         должает выполнять свою работу, не связываясь с устройством. Когда
         устройство готово к вводу/выводу, оно сигнализирует об этом цент-
         ральному процессору посредством аппаратуры.  Получив этот сигнал,
         центральный процессор сохраняет свое текущее состояние и вызывает
         подпрограмму  обслуживания  прерываний,  адрес которой хранится в
         таблице векторов прерываний.  Эта подпрограмма выполняет операцию
         ввода/вывода,  затем восстанавливает состояние машины и возвраща-
         ется в прерванную программу.  Учитывайте регистр символов, посту-
         пающих в коммуникационный порт персонального компьютера.  Органи-
         зовав где-нибудь  некоторые  ячейки  памяти  (буфер),  Вы  можете
         использовать  простую подпрограмму обработки прерываний,  которая
         быстро считывает символ из коммуникационного  порта  и  сохраняет
         его в следующей доступной ячейке памяти в буфере.  Символы не бу-
         дут утеряны в процессе считывания и сохранения  символа драйвером
         прерываний  перед поступлением следующего символа.  Эта несложная

                                      - 8-9 -
         задача достаточно проста для выполнения в короткие временные  ин-
         тервалы   между   поступающими  символами  при  скорости передачи
         9600 бод.  Прелесть этого метода заключается в том, что время об-
         работки главной программой символов, хранящихся в буфере, не име-
         ет значения. Конечно, существует риск переполнения буфера, но эта
         проблема  может быть решена простым увеличением его размера. Если
         этот способ не очень хорош,  то для избежания переполнения буфера
         можно использовать управление потоком с помощью XON/XOFF.
             Из наших рассуждений должно стать очевидным,  что управляемая
         прерываниями буферная связь с использованием управления потоком с
         помощью XON/XOFF, предпочтительнее упорядоченной связи.

                       Прерывания последовательного адаптера

             Последовательный адаптер  персонального компьютера может быть
         запрограммирован для прерывания  работы  центрального  процессора
         всякий  раз как только происходит одно из четырех событий (смотри
         рисунок 8-5). Универсальный асинхронный приемопередатчик присваи-
         вает приоритет каждому из четырех событий. В таблице 8-1 перечис-
         лены четыре прерывания.
                                                         Таблица 8-1
                       Прерывания последовательного адаптера
         ДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
             Приоритет  ¦  Идентификатор прерывания
         ДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                1       ¦  Состояние приемной линии (RLS)
                2       ¦  Доступность данных для приема (RDA)
                3       ¦  Регистр хранения передачи свободен (THRE)
                4       ¦  Состояние модема (MS)
         ДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

               Символы ASCII
             ЪДВДВДВДВДВДВДВД¬        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
             АДБДБДБДБДБДБДБДЩ        ¦  Регистр разрешения    1 2 3 4  ¦
                                      ¦         прерывания    ЪДВДВДВД¬ ¦
                                      ¦                       АДБДБДБДЩ ¦
                                      ¦                           ¦     ¦
                                      ¦                                ¦
                                      ¦  Идентификация        ЪДВДВДВД¬ ¦
                                      ¦    прерывания         АДБДБДБДЩ ¦
                     Последовательный ¦                 ЪДДДДДДДДЩ      ¦
                          адаптер     АДДДДДДДДДДДДДД¬                 ¦
                                                     ¦ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦
                                                     АДББББББББББББББББДЩ
                                                      
                                                     ЪД¬
                           Прерывание к 8259A       ДЩ АДД

                  Рис.8-5. Прерывания последовательного адаптера

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

             - Линия  отключается  (логический 0) на период времени больше
               требуемого для получения символа.

                                      - 8-10 -
             - Символ  получен до того,  как был считан предыдущий (ошибка
               выхода за границы).
             - Ошибка при проверке на четность.
             - При восстановлении символа из полученных битов не обнаружен
               стоповый бит (ошибка кадровой синхронизации).

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

             - Получает (посылает) сигнал "открыт для передачи" (CTS).
             - Показывает  свою  готовность установкой линии "набор данных
               готов" (DSR).
             - Получает  сигнал,  устанавливающий линию "индикатор кольца"
               (RI) в логическую 1.
             - Определяет  сигнал  переноса (тот тон,  который Вы слышите,
               когда вызываете номер и модем отвечает), устанавливая линию
               "определение сигнала приемной линии" (RLSD) в 1.

         Прерывание состояния  модема может быть сброшено чтением регистра
         состояния модема.
             Эти прерывания  могут  включаться и выключаться индивидуально
         установкой соответствующих битов в регистре  разрешения  прерыва-
         ний. В последовательно-параллельном адаптере фирмы IBM (также как
         и в асинхронном адаптере фирмы IBM) бит с именем OUT2  в регистре
         управления модемом должен быть установлен в 1 до того, как преры-
         вания универсального асинхронного приемопередатчика могут достичь
         центральный процессор.  Когда имеют место прерывания,  последова-
         тельный адаптер собирает их согласно приоритету и направляет пре-
         рывание  высшего  приоритета  в регистр идентификации прерывания.
         Адаптер прекращает отвечать на последующие прерывания равного или
         меньшего приоритета до тех пор, пока не определит, что текущее не
         обслужено подпрограммой обслуживания  прерываний. Программируемый
         контроллер прерываний 8259A.
             В  системах  MS-DOS  центральный  процессор   (микропроцессор
         80х86) непосредственно не принимает  прерывания,  поступающие  от
         аппаратных средств,  таких как последовательный адаптер. Прерыва-
         ния аппаратуры сначала обслуживаются чипом программируемого конт-
         роллера  прерываний  Intel 8259A.  8259A действует как "приемщик"
         центрального процессора.  Контроллер 8259A,  как  программируемое
         устройство, принимает до восьми различных прерываний и может мас-
         кировать (игнорировать) прерывания индивидуально.  8259A отвечает
         на  каждое незамаскированное или разрешенное прерывание и направ-
         ляет его центральному процессору при условии,  что никакое другое

                                      - 8-11 -
         прерывание высшего приоритета не обслуживается в настоящее время.
             Как контроллер 8259A присваивает приоритеты?  Как  и  универ-
         сальный асинхронный приемопередатчик имеет свой метод определения
         приоритетов прерываний последовательного адаптера, 8259A обладает
         своей схемой присваивания приоритетов прерываниям. Последователь-
         ный адаптер является только одним из аппаратных  средств, которые
         могут направлять прерывания контроллеру 8259A.  Каждое устройство
         жестко смонтировано или соединено проводниками с различными вход-
         ными устройствами, известными как входные устройства запроса пре-
         рываний (IRQ) контроллера 8259A.  Поэтому, обычно говорят об IRQ,
         присвоенном аппаратному прерыванию.  Другой характеристикой, свя-
         занной с IRQ прерывания,  является номер прерывания, используемый
         для обращения к отдельному прерыванию.  В персональном компьютере
         IBM PC этот номер равен восьми плюс IRQ. Когда имеет место преры-
         вание,  центральный процессор использует его номер в качестве ин-
         декса в таблице,  известной как таблица векторов прерываний (рас-
         положена  в  начале памяти),  которая содержит адрес подпрограммы
         обработки данного прерывания.  Так как контроллер 8259A связывает
         высшие приоритеты с низкими IRQ, аппаратные устройства, требующие
         максимального внимания,  имеют низкие IRQ. Таким образом, систем-
         ный таймер имеет IRQ0, клавиатура имеет IRQ1 и так далее.
             Несмотря  на то,  что MS-DOS 3.3 поддерживает четыре коммуни-
         кационных порта, с COM1 по COM4, эта поддержка не означает ничего
         кроме обладания четырьмя драйверами с этими  именами,  каждый  из
         которых  поддерживается  безбуферно  и  только упорядоченным вво-
         дом/выводом.  Так как мы  интересуемся  управляемыми прерываниями
         последовательным вводом/выводом, детали поддержки системой MS-DOS
         коммуникационных портов не относятся к данному вопросу.
             В персональном  компьютере  IBM  PC  только два первых порта,
         COM1 и COM2,  имеют определенные номера IRQ и номера  прерываний.
         Другие  последовательные порты,  такие как COM3 и COM4 могут быть
         использованы для управляемого прерываниями ввода/вывода после ус-
         тановки  адаптеров  и  присваивания номеров IRQ посредством соот-
         ветствующей установки перемычек. Как только станет известен номер
         IRQ, программирование портов COM3 и COM4 выполняется таким же об-
         разом,  как и портов COM1 и COM2. Более того, до конца этой главы
         мы будем рассматривать только порты COM1 и COM2.
             Двум последовательным портам COM1 и COM2 присвоены  соответс-
         твенно IRQ4  и IRQ3,  то есть номера прерываний 12 и 11 (десятич-
         ные).  Между прочим,  номера прерываний должны быть известны, так
         как  функциональные вызовы DOS (посредством программного прерыва-
         ния 21h) с функциональными номерами 35h и 25h могут быть  исполь-
         зованы соответственно для получения и установки векторов прерыва-
         ний.
             Есть еще  несколько моментов,  о которых необходимо упомянуть
         перед началом разговора о программировании последовательных  пор-
         тов  для управляемого  прерываниями  ввода/вывода. Микропроцессор
         80х86 автоматически делает невозможными все прерывания в то время,
         когда  он передает управление обслуживающей подпрограмме текущего
         прерывания.  Несмотря на то, что контроллер 8259A во время обслу-
         живания прерывания задерживает последующие прерывания того же или
         меньшего приоритета, прерывания старшего приоритета все еще полу-
         чают подтверждение о приеме, если установлен флаг прерывания. Ес-
         ли мы вновь немедленно не разрешим прерывание до начала  обслужи-
         вания  прерывания  от  последовательного  порта,  многие жизненно
         важные системные функции,  передаваемые прерываниями  (такие  как
         системный таймер,  клавиатура и контроллер диска),  обслуживаться

                                      - 8-12 -
         не будут.  Важно, поэтому, используя команду STI (установить флаг
         прерывания)  переключить прерывание как только обслуживающая под-
         программа примет на себя управление. Это предоставит  возможность
         таймеру,  клавиатуре  и  контроллеру диска прерывать подпрограмму
         обслуживания последовательного  порта,  позволяя  функционировать
         другим устройствам.
             Как мы сможем сообщить контроллеру 8259A о том, что обработка
         последовательного прерывания завершена?  Наша служебная  подпрог-
         рамма должна направить 8259A команду "конец прерывания" (EIO) пе-
         ред возвращением управления центральному процессору.  Несмотря на
         то,  что существуют способы требования EIO для различных IRQ, для
         схемы приоритета,  используемой в персональном компьютере, доста-
         точно направить контроллеру 8259A то,  что известно как "неспеци-
         фический" EOI (код 20h).  Название "неспецифический" вытекает  из
         того,  что эта команда не определяет, какое прерывание обслужива-
         лось. Она просто говорит контроллеру, что обслуживание прерывания
         высшего приоритета завершено. Это разрешает обслуживание прерыва-
         ний того же или высшего IRQ.

                        Программирование контроллера 8259A

             Управляемый прерываниями ввод/вывод требует правильной  уста-
         новки контроллера 8259A.  В противном случае, прерывания, генери-
         руемые последовательным адаптером,  никогда не будут приняты мик-
         ропроцессором  80х86.  Таким образом,  важно выяснить сначала то,
         как мы можем программировать 8259A.
             Как  вся  аппаратура  персонального  компьютера,  контроллер
         8259A программируется посредством двух имен  команд  (регистров).
         Они  расположены в адресах 20h и 21h порта ввода/вывода соответс-
         твенно (рисунок 8-6). Регистр с адресом 21h используется исключи-
         тельно для маскирования прерываний.  Прерывание маскируется (т.е.
         не принимается) в том случае,  если бит,  соответствующий  своему
         IRQ  (считая справа налево,  причем самому правому биту присвоено
         значение IRQ0),  установлен в логическую единицу.  Порт по адресу
         20h  используется  для направления команды прерывания контроллеру
         8259A.  Как мы отметили ранее, в системах MS-DOS это делается за-
         писью 20h в этот порт.
                                      Прерывание к CPU ДДД¬
                                                          ¦
         Ъ Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д ДБД Д Д Д Д Д ¬
                        ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬ конец
         ¦    Порт 20h  ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦ прерывания   ¦
                        АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ EOI=20h
         ¦                             АДДДД 20h                       ¦
            8259A                                          IRQ
         ¦               ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬ 1=a.        ¦
                         ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 3.¦ 4.¦ 5.¦ 6.¦ 0=b.
         ¦               АДДДБДДДБДДДБДВДБДВДБДДДБДДДБДДДЩ             ¦
           Программируемый контроллер прерываний
         А Д Д Д Д Д Д Д Д Д Д Д Д Д Д † Д † Д Д Д Д Д Д Д Д Д Д Д Д Д Щ
                                       ¦   ¦         ¬ Последовательные
                                       ¦   АДДД COM2:¦ порты
                                       АДДДДДДД COM1:¦ персонального
                                                     Щ компьютера
               Рис.8-6. Программируемый контроллер прерываний 8259A

         a. - замаскировать прерывание; b. - разрешить прерывание;

                                      - 8-13 -

             В системах  MS-DOS  первому  последовательному  порту  (COM1)
         присвоен номер IRQ4 (прерывание номер 12),  в то время как второй
         (COM2) имеет IRQ3 (прерывание 11).  Как указывалось ранее,  порты
         COM3 и COM4 могут обрабатываться таким же  образом,  предполагая,
         что  известны номера IRQ,  присвоенные этим портам во время уста-
         новки (только порты COM1 и COM2 имеют заранее  присвоенные номера
         IRQ).  Поэтому,  контроллер 8259A может быть запрограммирован для
         получения прерываний от порта COM1 чтением с порта 21h и обратной
         записью содержания, логически умноженного на EFh. Прерывания пор-
         та COM1 могут быть замаскированы не только повторением предыдущих
         действий, но логическим сложением с 10h, вместо логического умно-
         жения.  Таким образом, контроллер 8259A может быть запрограммиро-
         ван  для  разрешения  прерываний  порта COM1 с помощью следующего
         фрагмента программы

         IN     AL,21H       ;получить текущую  маску прерывания
         AND    AL,EFH       ;разрешить IRQ4
         OUT    21H,AL       ;снова записать его обратно

             Когда прерывания  порта  COM1 снова выключаются,  8259A может
         быть запрограммирован следующим образом

         IN     AL,21H       ;получить текущую маску прерывания
         OR     AL,10H       ;запретить IRQ4
         OUT    21H,AL       ;снова записать его обратно

             Подобное программирование устройств,  выполняемое считыванием
         содержание регистра с последующей записью его обратно с соотвест-
         вующе измененным битом, рекомендуется ввиду того, что мы не нару-
         шаем предварительной установки битов.
             Кроме разрешения  и запрещения приема прерываний,  контроллер
         8259A должен быть проинформирован о завершении обработки обычного
         прерывания.  Как указывалось ранее,  это выполняется направлением
         20h в адрес 20h порта ввода/вывода следующим образом:

         MOV      AL,20H    ;код конца прерывания
         OUT      20H,AL    ;в порт 20Н контроллера 8259A


                 Использование средств MS-DOS для программирования
                              последовательного порта

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

                       Драйвер, TSR или автономная программа

             Существует несколько способов получения доступа к  последова-
         тельному адаптеру в системе MS-DOS.  Вы можете управлять последо-
         вательным портом через устанавливаемый драйвер устройства, выпол-
         няющий  ввод/вывод через этот порт.  В главе 6   "Устанавливаемые

                                      - 8-14 -
         драйверы устройств"   рассматриваются  детали  разработки  такого
         драйвера.  Главным  недостатком такого подхода являются издержки,
         связанные с доступом к драйверу через DOS, а достоинством то, что
         любая  программа,  которая знает о Вашем драйвере,  может его ис-
         пользовать.  Если Вы выбрали этот способ,  то можете обеспечить в
         драйвере возможности IOCTL таким образом,  что связные параметры,
         такие как скорость передачи в бодах и длина слова, могут быть ус-
         тановлены вызовами DOS IOCTL (номер функции DOS 44h).
             Вторым подходом является установка программы TSR (завершенная
         и оставленная резидентно),  которая используется с помощью преры-
         вания BIOS RS-232C (14h) и расширяет свою функциональность  обес-
         печением управляемого прерываниями ввода/вывода. Этот метод также
         дает любой программе доступ к последовательному порту посредством
         Вашего  драйвера  TSR в то время как Вы подтверждаете необходимые
         установки регистра при использовании новых коммуникационных функ-
         ций TSR.  Механизм доступа будет таким же, как вызов функции BIOS
         RS-232C, который мы вскоре опишем.
             Третий метод  заключается  в разработке автономной программы,
         которая включает в себя служебную подпрограмму обработки прерыва-
         ний  последовательного  порта.  В  этом случае,  при запуске этой
         прикладной программы,  Вы можете установить обработчик прерываний
         последовательного  порта  и сбросить его после прекращения работы
         программы. Этот способ создает возможности организации высокоско-
         ростного (9600 бод) последовательного ввода/вывода ввиду наличия
         в нем меньшего количества недостатков по сравнению с двумя други-
         ми методами.
             Независимо  от того,  какой подход  Вы  выберите,  управление
         последовательным портом останется таким же.  Далее мы уделим осо-
         бое внимание деталям.

                   Использование BIOS для последовательной связи

             Вы, вероятно, спросите, можно ли реализовать эффективный ввод
         /вывод через BIOS. К сожалению, нет. BIOS не предоставляет эффек-
         тивной возможности  управления  последовательным  адаптером.  Для
         программирования  последовательного  адаптера  BIOS имеет функцию
         RS-232C, доступную через прерывание 14h. К сожаления, эта функция
         поддерживает  только упорядоченный ввод/вывод,   который не очень
         эффективен ввиду недостатков, изложенных ранее. Тем не менее, эта
         функция идеальна для установки таких параметров коммуникационного
         порта, как скорость передачи в бодах, длина слова и стоповый бит,
         использующий прерывание 14h BIOS.

            Установка коммуникационных параметров с использованием BIOS

             Даже при использовании в последовательном  вводе/выводе  BIOS
         не  столь эффективна как управляемый прерываниями подход. Полезно
         посмотреть,  как коммуникационные параметры (скорость передачи  в
         бодах, длина слова, четность и стоповые биты) могут быть установ-
         лены с использованием функций BIOS RS-232C, доступных по прерыва-
         нию 14h.
             Прерывание 14h с нулем в АН устанавливает параметры  последо-
         вательного порта.  Номер порта должен находиться в DX.  Нуль в DX
         указывает на порт COM1,  в то время,  как  единица  указывает  на
         COM2. Выбранные коммуникационные параметры направляются в регистр
         AL в упакованном формате, показанном на рисунке 8-7. Скорость пе-
         редачи  определяется  3-битовым  значением,  четность - 2-битовым

                                      - 8-15 -
         значением,  число стоповых битов - одним битом и  длина  слова  -
         2-битовым значением. В таблице 8-2 показаны кодированные значения
         каждого коммуникационного параметра. Учтите, что скорость переда-
         чи  драйверов  через  порт  COM в системе DOS 3.3 может достигать
         19200 бод,  в то время,  как ROM-BIOS ограничивается 9600 бодами.
         Для достижения скорости передачи,  не указанной в таблице 8-2, Вы
         можете использовать возможность программирования скорости переда-
         чи  универсального асинхронного приемопередатчика,  рассмотренную
         ранее.
                   Скорость                Длина
                 Ъпередачи в ВЧет-стьВ a.В слова ¬
                 ¦  бодах    ¦       ¦   ¦       ¦
                 ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
                 ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦
                 АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
                     Боды      Четность   Стоп-биты  Длина слова
                 ЪДДДДДВДДДДД¬  ЪДДВДД¬   ЪДДВДД¬    ЪДДДДВДДДД¬
                 ¦ 000 ¦ 110 ¦  ¦00¦  ¦   ¦0 ¦ 1¦    ¦ 10 ¦ 7  ¦
                 ГДДДДД†ДДДДДґ  ГДДґb.¦   ГДД†ДДґ    ГДДДД†ДДДДґ
                 ¦ 001 ¦ 150 ¦  ¦10¦  ¦   ¦1 ¦ 2¦    ¦ 11 ¦ 8  ¦
                 ГДДДДД†ДДДДДґ  ГДД†ДДґ   АДДБДДЩ    АДДДДБДДДДЩ
                 ¦ 010 ¦ 300 ¦  ¦01¦c.¦
                 ГДДДДД†ДДДДДґ  ГДД†ДДґ
                 ¦ 011 ¦ 600 ¦  ¦11¦d.¦
                 ГДДДДД†ДДДДДґ  АДДБДДЩ
                 ¦ 100 ¦1200 ¦
                 ГДДДДД†ДДДДДґ
                 ¦ 101 ¦2400 ¦
                 ГДДДДД†ДДДДДґ
                 ¦ 110 ¦9600 ¦
                 ГДДДДД†ДДДДДґ
                 ¦ 111 ¦     ¦
                 АДДДДДБДДДДДЩ
           Рис.8-7. Коммуникационные параметры,  упакованные в одиночный
                  байт в формате, требуемом прерыванием 14h BIOS

            a.- стоповые биты; b. - нет; c. - нечетность; d. - четность


                                                         Таблица 8-2
                      Кодированные значения коммуникационных
                          параметров для прерывания 14h
         ДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДД
         Имя параметра ¦ Фактическое значение ¦ Кодированное  значение
         ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДД
         Скорость пе-  ¦          110         ¦           0
         редачи в бодах¦          150         ¦           1
                       ¦          300         ¦           2
                       ¦          600         ¦           3
                       ¦         1200         ¦           4
                       ¦         2400         ¦           5
                       ¦         4800         ¦           6
                       ¦         9600         ¦           7
                       ¦                      ¦
         Четность      ¦           Нет        ¦        0 или 2
                       ¦       Нечетность     ¦           1
                       ¦        Четность      ¦           3

                                      - 8-16 -

         Стоповые биты ¦           1          ¦           0
                       ¦           2          ¦           1
                       ¦                      ¦
         Длина слова   ¦           7          ¦           2
                       ¦           8          ¦           3
         ДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДД

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

         pckd_commparams = (baudrate << 5) | (parity <<3) |
                           (stopbits << 2) | (wordlength);

         Мы использовали  операторы сдвига бита и поразрядного логического
         сложения  языка  Си.  Переменные  baudrate,  parity,  stopbits  и
         wordlength  должны быть кодированными значениями коммуникационных
         параметров из последней колонки таблицы 8-2. Как только параметры
         примут такой формат, Вы можете вызывать BIOS прерыванием 14h. Ис-
         пользование функции int86 компилятора Microsoft  С иллюстрируется
         следующим фрагментом программы:

         #include <dos.h>
         #define  BIOS_RS232  0x14
                           /* номер прерывания для обслуживания BIOS */
         static union REGS xr, yr;
             .
             .
             .
         xr.h.ah = 0;        /* номер функции для вызова BIOS RS-232 */
         xr.h.al = pckd_commparams;             /* связные параметры */
         xr.x.dx = port_number;  /* 0 означает COM1, 1 означает COM2 */
         int86(BIOS_RS232, &xr, &yr);               /* сделать вызов */
             .
             .
             .

             Компилятор Microsoft C версии 5.0 облегчает вызов подпрограмм
         BIOS: функция _bios_serialcomm служит интерфейсом между Вашей  Си
         программой и прерыванием BIOS 14h.  Например,  если  Вы  выберете
         8-битовую  длину  слова,  1 стоповый бит,  отсутствие проверки на
         четность и скорость передачи 300 бод, то достаточно вызвать

         _bios_serialcom(_COM_INIT, COM1, (_COM_CHR8 | _COM_STOP1 |
                                           _COM_NOPARITY | _COM_300) );

         Вызов функции _bios_serialcom

         status = _bios_serialcomm(service_code, port_number, data);

         принимает три параметра,  выраженных целым без знака и возвращает
         код  состояния  того  же  типа данных для демонстрации результата
         требуемой операции.  Параметр service_code используется для опре-
         деления  требуемой  операции  и  port_number принимает значение 0
         (COM1) или 1 (COM2).  Значение data зависит от требуемой  услуги.
         Подробности использования этой функции изложены в Microsoft C 5.0

                                      - 8-17 -
         Run-time Library Reference.

                     Получение адреса последовательного порта

             Другим полезным встроенным свойством BIOS является то, что на
         этапе самотестирования при включении (POST) она проверяет наличие
         последовательных адаптеров COM1/COM2 (хотя MS-DOS 3.3 поддержива-
         ет порты COM3 и COM4, BIOS распознает только COM1 и COM2) и, если
         находит тот или другой,  адрес первого регистра каждого  адаптера
         заносится  в  область  памяти,  начиная со смещения нуля сегмента
         14h.  Так как в персональном компьютере 20-битовый физический ад-
         рес равен 10h * 16-битовый сегмент + 16-битовое смещение,  и если
         Ваша система MS-DOS имеет один последовательный порт, назначенный
         как COM1, то тогда слово в физической ячейке 400h будет содержать
         3F8h (если так же присутствует COM2, следующее слово в ячейке 402
         h будет содержать 2F8h).  Таким образом, Вы можете получить адрес
         последовательного адаптера из этой области данных BIOS на  смеще-
         нии 0 и сегменте 40h.  Например,  в Microsoft C Вы можете устано-
         вить базовый адрес порта следующим образом:

         #define BIOS_DATA ((short far *)(0x400000L))
         static short comport,    /* для базового адреса порта */
                port_number;      /* 0 для COM1, 1 для COM2 */
            .
            .
            .
         comport = *(BIOS_DATA + port_number);

         if(comport == 0) /* последовательный адаптер не установлен */
         {
             printf("Последовательный адаптер не установлен!\n";
             exit(1);
         }

             Так как инициализируется переменная comport,  все другие  ре-
         гистры последовательного адаптера могут быть доступны прибавлени-
         ем соответствующих смещений к базовому адресу.  В языке Си Вы мо-
         жете  использовать  директиву препроцессора #define для установки
         адресов этих регистров.  Например, если инициализируется comport,
         Вы  имеете  возможность  обратиться к регистрам последовательного
         порта по их именам, определив их следующим образом:

         #define IER (comport + 1) /* регистр разрешения прерывания */
         #define IIR (comport + 2) /* определение прерывания */
         #define LCR (comport + 3) /* регистр управления линией */
         #define MCR (comport + 4) /* регистр управления модемом */
         #define LSR (comport + 5) /* регистр состояния линии */
         #define MSR (comport + 6) /* регистр состояния модема */


              Настройка на управляемый прерываниями последовательный
                                    ввод/вывод

             После получения  базового адреса порта из области данных BIOS
         Вы должны настроить последовательный порт и установить обработчик
         прерываний перед началом управляемого прерываниями последователь-
         ного ввода/вывода.  Номер прерывания и IRQ прерывания  последова-

                                      - 8-18 -
         тельного порта зависит от того, какой порт используется, COM1 или
         COM2. Как только Вы получили номер прерывания, Вы должны получить
         адрес текущего обработчика и сохранить его.  Таким образом, после
         выхода из программы Вы можете восстановить первоначальное  значе-
         ние вектора прерываний. Функции MS-DOS 35h и 25h, соответственно,
         получают и устанавливают обработчики для заданных номеров  преры-
         ваний.  Для  этой  цели  Microsoft C  предоставляет  подпрограммы
         _dos_getvect и _dos_setvect. Используя язык Си Вы можете проделать
         это следующим образом:

         short int_number;    /* номер прерывания для связного порта */
         void interrupt far s_inthndlr(void);
                              /* установить обработчик */
         static void  (interrupt far *old_handler)()
                              /* место для прежнего */
             .
             .
             .
         /* получить вектор прежнего прерывания и сохранить его */
             old_handler = _dos_getvect(int_number);
         /* установить новый обработчик с именем s_inthndlr
          * запретить прерывания во время замены обработчика
          */
             _disaple();
             _dos_setvect(int_number, s_inthndlr);
             _enable();

             В приведенном примере мы представили обработчик  как  функцию
         типа interrupt,  которая является новым ключевым словом, содержа-
         щимся в Microsoft C 5.0.  В следующем разделе продемонстрировано,
         каким  образом  атрибут interrupt позволяет Вам писать обработчик
         прерываний непосредственно в Microsoft C 5.0 (Turbo C  1.5  имеет
         такую же возможность).
             Следует обратить внимание на использование функций _disable и
         _enable.  Эти две функции соответствуют ассемблерным командам STI
         и CLI.  Таким образом,  мы выключаем прерывания во время перехода
         от  одного  обработчика последовательных прерываний к другому.  С
         другой стороны,  прерывание,  поступающее во время  переключения,
         может привести к тому, что может произойти сбой центрального про-
         цессора, так как вектор прерывания не являлся адресом какого-либо
         действующего обработчика.
             После того,  как обработчик прерываний займет свое место,  Вы
         можете установить коммуникационные параметры и разрешить последо-
         вательному порту генерировать прерывания.  Вы также должны разре-
         шить распознавание этих прерываний контроллером 8259A. Еще раз Вы
         должны запретить прерывания до тех пор,  пока порт и 8259A не бу-
         дут  готовы.  Вот  как  мы  можем  проделать это с использованием
         Microsoft C 5.0.

         /* разрешает маску, зависящую от порта */
         short intmask,  int_enable_mask;
             .
             .
             .
         /* включает прерывания коммуникационного порта.
          * устанавливает 8259A
          */

                                      - 8-19 -
             _disable();
         /* устанавливает регистр управления модемом (порт =  MCR) */
             outp(MCR, MCRALL);
         /* разрешает все прерывания последовательной платы
          * (порт = IER)
          */
             outp(IER, IERALL);
         /* считывает регистр маски прерывания 8259A и записывает его
          * обратно после логического умножения с _int_enable_mask
          */
             intmask = inp(P8259_1) & int_enable_mask;
             outp(P8259_1, intmask);
             _enable();

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


                   Обработка прерываний последовательного порта

             Наш обработчик, s_inthndlr, будет вызван при генерации после-
         довательным портом прерывания.  Мы  должны  немедленно  разрешить
         прием системой последующих прерываний таким образом, чтобы другие
         приоритетные задачи (такие как таймер) могли обрабатываться  мик-
         ропроцессором.
             Следующий шаг - идентификация точной причины, вызвавшей гене-
         рацию прерывания последовательного порта.  Для получения информа-
         ции Вы должны считать содержание регистра идентификации  прерыва-
         ния  (IIR).  Как  только  определится  причина прерывания,  можно
         выполнять его обработку,  как описывалось в разделе,  посвященном
         универсальному асинхронному приемопередатчику.
             Так как последовательный порт способен генерировать  прерыва-
         ние в то время,  пока Вы обрабатываете другое,  необходимо прове-
         рять бит 0 (последний значащий бит) IIR на это условие. Если этот
         бит равен нулю,  то имеется другое прерывание и его следует обра-
         ботать.  С другой стороны, если значение бита равно единице, оче-
         редные прерывания отсутствуют.  В этом случае Вы должны направить
         контроллеру 8259A сигнал "конец прерывания" и выйти из обработчи-
         ка.  Таким  образом,  обработчик  представляет  собой бесконечный
         цикл, который продолжает обрабатывать последовательные прерывания
         до тех пор, пока не прекратится их поступление. В Microsoft C 5.0
         обработчик может быть реализован как

         void interrupt far s_inthndlr(void)
         {
             int c;
             register int int_id, intmask;

         /* прерывания разрешаются немедленно */
             _enable();

             while (TRUE)
             {
         /* чтение регистра идентификации прерываний, IIR */
                  int_id = inp(IIR);
                  if (bit0(int_id) == 1)

                                      - 8-20 -
                  {
         /* если бит 0 равен 1, тогда прерывания не поступают. послать
          * сигнал конец прерывания программируемому контроллеру
          * прерываний 8259A и затем вернуться.
          */
                      outp(P8259_0, END_OF_INT);
                      return;
                  }
         /* если есть прерывание получения данных, разрешить прерывания
          * для "свободен регистр хранения передачи"
          */
                  if (int_id) >= RXDATAREADY)
                                    turnon_int(THEREINT,intmask);

         /* обработать прерывание в соответствии с идентификатором.
          * Следующий список составлен согласно возрастанию приоритета.
          */
                 switch (int_id)
                 {
                    case MDMSTATUS:  /* состояние готовности модема */
                                       .
                                       .
                                       .
                                      break;
                    case TXREGEMPTY:  /* послать символ */
                                       .
                                       .
                                       .
                                      break;
                    case RXDATAREADY: /* читать символ */
                                       .
                                       .
                                       .
                                      break;
                    case RLINESTATUS: /* читать состояние линии */
                                       .
                                       .
                                       .
                                      break;
         /* пропустить, если идентификатор не является одним из
          * перечисленных */
                 }
             }
         }

             Обратите внимание,  что мы  воспользовались  ключевым  словом
         interrupt,имеющемся  в Microsoft C 5.0 и позволяющем писать обра-
         ботчик на языке Си.  Это ключевое слово используется как специфи-
         катор функции,  которую Вы желаете установить в качестве обработ-
         чика прерываний определенного номера прерывания.  При  трансляции
         функции  с атрибутом interrupt компилятор генерирует код для пер-
         воначального помещения в стек регистров AX,  CX,  DX, BX, SP, BP,
         SP,  SI,  DI,  DS и ES. Затем он устанавливает регистр DS в режим
         ссылки на сегмент данных указанной функции.  После этой начальной
         последовательности  следует код функции.  В заключении компилятор
         использует команду IRET вместо обычной команды RET для  выхода из
         функции.  Данный пример является типичным для использования атри-

                                      - 8-21 -
         бута interrupt.  В компиляторе Turbo C также имеется это ключевое
         слово, но помещение регистров в стек происходит в другом порядке.
             Когда Вы пишете обработчик прерываний на языке Си, то необхо-
         димо соблюдать такие же предосторожности, как и при написании об-
         работчиков на языке ассемблера.  Например, Вы не должны использо-
         вать какую-либо библиотечную подпрограмму, вызывающую функцию DOS
         (доступ к ним осуществляется посредством команды прерывания 21h).
         Такими  функциями  в  языке Си являются подпрограммы ввода/вывода
         файлов.  С другой стороны,  подпрограммы, находящиеся в категории
         подпрограмм   манипулирования   строками,   хранятся   в  функции
         interrupt.

                          Очереди обработчика прерываний

             Целью обработчика прерываний последовательного порта является
         скорейшее сохранение поступающих символов.  Наилучшим образом это
         достигается  с использованием буфера.  Прикладная программа может
         восстанавливать из этого буфера символы со  своей  скоростью,  не
         беспокоясь о потере какого-либо символа,  потому что она работает
         недостаточно быстро.  Посылаемые символы также могут направляться
         обработчику прерываний через другой буфер.
             Каждый из этих буферов должен вести себя как  линия  проверки
         регистра.  Символы  поступают в линию один за другим и программа,
         считывающая символы,  принимает первый из них и обрабатывает его,
         затем принимает следующий и так далее. Буфер такого типа известен
         как "первый пришел - первый вышел" (FIFO) буфер.  Это также назы-
         вается очередью.
             На рисунке 8-8 показана  концептуальная  реализация  очереди.
         Очередь, естественно, имеет начало и конец. В реальной реализации
         размер очереди,  т.е.  максимального числа символов,  которое она
         может содержать,  фиксировано. Удобно считать ячейки памяти, свя-
         занные с очередью,  кругом,  таким образом,  что пройдя последнюю
         ячейку,  мы возвращаемся к первой. Это делает эффективным исполь-
         зование ограниченного пространства, доступного для очереди. Такая
         реализация очереди называется циклической.


                 ЪДДДВДДДВДДДВДДДВДДДВДДД¬
                 ¦   ¦   ¦   ¦   ¦   ¦   ¦
                 ГДДД†ДДДБДДДБДДДБДДД†ДДДґ
                 ¦   ¦               ¦   ¦
                 ГДДДґ               ГДДДґ
                 ¦   ¦Задние   Первые¦   ¦
                 ГДДД†ДДД¬       ЪДДД†ДДДґ
                 ¦   ¦   ¦Д¬   ЪД¦   ¦   ¦
                 АДДДБДДДЩ ¦   ¦ АДДДБДДДЩ
                          ¦   ¦
                       Вход    Выход
                     Рис.8-8. Циклический буфер FIFO (очередь)

                          Уборка перед закрытием магазина

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

             int intmask;
             .
             .
             .
         /* Запретить прерывания на время очистки */
             _disable();
         /* Сначала сбросить регистр разрешения прерываний порта */
             outp(IER,IEROFF);
         /* Выключить все биты регистра управления модемом */
             outp(MCR,MCROFF);

         /* Затем запретить распознавание контроллером 8259A прерываний
            последовательного порта */
             intmask = inp(P8259_1) | int_disable_mask;
             outp(P8259_1, intmask);

         /* Восстановить первоначальный вектор прерываний */
             _dos_setvect(int_number, old_handler);

         /* Снова разрешить прерывания */
             _enable();

                                 Пример программы

             Мы описали аппаратные средства последовательного порта, указа-
         ли, какие действия необходимо выполнить для программирования порта
         в целях организации эффективного  управляемого  прерываниями  вво-
         да/вывода.  Осталось только объединить отдельные части,  для того,
         чтобы показать, каким образом создается завершенная коммуникацион-
         ная  программа.  Мы  делаем  это в листинге 8-1,  который содержит
         основную коммуникационную программу,  написанную  на  Microsoft  C
         версии 5.0.
            Листинг 8-1. Коммуникационная программа на Microsoft C 5.0
         ------------------------------------------------------------------

         /*
          * Имя файла:                 SERIO.C
          * Цель:                      Иллюстрация программирования
          *                            последовательного порта в систе-
          *                            мах MS-DOS. Эта версия разрабо-
          *                            тана на персональном компьтере
          *                            IBM PC-AT с последовательным
          *                            адаптером фирмы IBM.
          *                            Использовалась операционная
          *                            система DOS 3.1.
          * Автор:                     Наба Баркакати, март 1988
          * Язык:                      Microsoft C 5.0
          * Модель памяти:             Большой емкости
          * Транслировать/компоновать: CL /AL /Gs serio.c
          */

                                      - 8-23 -
         /*----------------------------------------------------------*/
         #include <stdio.h>
         #include <ctype.h>
         #include <dos.h>
         #include <bios.h>
         #include <conio.h>

         #define  TRUE 1
         #define  FALSE 0
         #define  EOS  '\0'

         #define  CONTROL(x)  (x-0x40)
         #define  ESC_KEY     CONTROL('[')

         /* Определить коммуникационные параметры */
         #define COM_PARAMS (_COM_CHR8 | _COM_STOP1 | \
                                 _COM_NOPARITY |_COM_1200)


         /* Определить размеры приемного и передающего буферов */
         #define RXQSIZE 512
         #define TXQSIZE 512

         /* Определения для программируемого контроллера
          * прерываний 8259
          */
         #define P8259_0 0x20    /* регистр управления прерыванием */
         #define P8259_1 0x21    /* регистр маски прерывания */
         #define END_OF_INT 0x20 /* не определенный EIO */

         /* Определить коды ASCII XON и XOFF */
         #define XON_ASCII   (0x11)
         #define XOFF_ASCII  (0x13)

         /* Обратиться к области данных BIOS по адресу 400h */
         #define BIOS_DATA  ((int far *)(0x400000L))

         /* Адресом коммуникационного порта является короткое целое
          * 'comport'. Эта переменная инициализируется чтением из
          * области данных BIOS на сегменте 0х40.
          */
         #define IER (comport + 1) /* регистр разрешения прерываний */
         #define IIR (comport + 2) /* идентификация прерывания */
         #define LCR (comport + 3) /* регистр управления регистром */
         #define MCR (comport + 4) /* регистр управления модемом */
         #define LSR (comport + 5) /* регистр состояния линии */
         #define MSR (comport + 6) /* регистр состояния модема */

         /* Коды разрешения отдельных прерываний */
         #define RDAINT  1
         #define THREINT 2
         #define RLSINT  4
         #define MSINT   8


         /* Значение регистра управления модемом */
         #define MCRALL  15  /* (DTR, RTS, OUT1 и OUT2 = 1) */

                                      - 8-24 -
         #define MCROFF  0   /* все сброшено */

         /* Значение регистра разрешения прерывания для его
          * включения/выключения
          */
         #define IERALL  (RDAINT+THREINT+RLSINT+MSINT)
         #define IEROFF  0

         /* Несколько масок для сброса прерываний */
         #define THEREOFF 0xfd

         /* Номера идентификатора прерываний */
         #define MDMSTATUS   0
         #define TXREGEMPTY  2
         #define RXDATAREADY 4
         #define RLINESTATUS 6

         /* Флаги управления потоком XON/XOFF */
         #define XON_RCVD   1
         #define XOFF_RCVD  0
         #define XON_SENT   1
         #define XOFF_SENT  0

         /* Высший и низший коэффициенты для триггера xon-xoff */
         #define HI_TRIGGER(x)  (3*x/4)
         #define LO_TRIGGER(x)  (x/4)

         /* Функция получения нулевого бита целого */
         #define bit0(i)   (i & 0x0001)

         /* Макрокоманда для включения прерывания, "номер разрешения
          * прерывания" которого "i", в противном случае оно запрещает-
          * ся.  Например, прерывание THRE запрещается, если XOFF полу-
          * чен от удаленной системы.
          */

         #define turnon_int(i,j) \
                 if(((j=inp(IER))&i)==0)outp(IER,(j|i))

         #define report_error(s)  fprintf(stderr,s)
         typedef struct QTYPE  /* структура данных для очереди */
         {
                int  count;
                int  front;
                int  rear;
                int  mexsize;
                char *data;
         }   QTYPE;

         static char rxbuf[RXQSIZE], txbuf[TXQSIZE];

         static QTYPE rcvq = {0, }-1, -1, RXQSIZE, rxbuf},
                      trmq = {0, }-1, -1, TXQSIZE, txbuf};

         /* Признаки общего состояния */
         int s_linestatus, s_modemstatus;


                                      - 8-25 -
         static  QTYPE *txq = &trmq, *rxq = &rcvq;
         static  short comport=0,
                       enable_xonxoff = 1,
                       rcvd_xonxoff = XON_RCVD,
                       sent_xonxoff = XON_SENT,
                       send_xon = FALSE,
                       send_xoff = FALSE,
                       int_number = 12;
                       int_enable_mask = 0xef,
                       int_disable_mask = 0x10;

         /* Прототипы функций */
         int s_sendchar(int);
         int s_rcvchar(void);
         int s_setup(short, unsigned);
         int s_cleanup(void);

         char *q_getfrom( QTYPE *, char *);
         int   q_puton( QTYPE *, char *);

         void interrupt far s_inthndlr(void);

         static void  s_rda(void);
         static void  s_trmty(void);
         static void  (interrupt far *old_handler)();
         /*----------------------------------------------------------*/
         main(int argc, char **argv)
         {
             int ch, port_number = 0;

         /* Получить номер порта из командной строки */
             if(argc > 1) port_number = atoi(argv[1]) - 1;
             printf("\nSERIO -- Последовательный ввод/вывод \
         c параметрами 1200,8,N,1 через порт COM%d\n", port_number+1);
             printf("\nСвязь ...\n");

         /* Сначала установить последовательный порт */
             s_setup(port_number, COM_PARAMS);

         /* Последующий бесконечный цикл имитирует терминал.
          * Клавиша Escape служить для общего сброса и возврата.
          */

             while (TRUE)
             {
                  if ((ch = s_rcvchar()) != -1) putch(ch);
                  if ( kbhit() != 0)
                  {
                      ch = getch();
                      if (ch == ESC_KEY)
                      {
                            s_cleanup();
                            return;
                      }

                      else
                          s_sendchar(ch);

                                      - 8-26 -
                  }     /* конец проверки kbhit() */
             }
         }
         /*---------------------------------------------------------*/
         /* s _ i n t h n d l r
          * Обработчик всех прерываний последовательного порта.
          */
         void interrupt far s_inthndlr(void)
         {
             int c;
             registr int int_id, intmask;

         /* Немедленно разрешить прерывания */
             _enable();

             while (TRUE)
             {
         /* Считать регистр идентификации прерывания , IIR */
                int_id = inp(IIR);
                if (bit0(int_id) == 1)
                {
         /* Если бит 0 - 1, то прерывания не ожидаются. Направить прог-
          * раммируемому контроллеру прерываний сигнал  "конец прерыва-
          * ния" и вернуться.
          */
                    outp(P8259_0, END_OF_INT);
                    return;
                }
                if (int_id >= RXDATAREADY)
                                turnon_int(THREINT,intmask);
         /* Обработать прерывание в соответствии с идентификатором.
          * Следующий перечень составлен в соответствии с возрастанием
          * приоритета.
          */


                 switch (int_id)
                 {
                    case MDMSTATUS:    /* считать состояние модема */
                                       s_modemstatus = inp(MSR);
                                       break;
                    case TXREGEMPTY:   s_trmty();
                                       break;
                    case RXDATAREADY:  s_rda();
                                       break;
                    case RLINESTATUS:  /* читать состояние линии */
                                       s_linestatus = inp(LSR);
                                       break;
         /* пропустить, если идентификатор не является одним из
          * перечисленных */
                 }
             }
         }
         /*---------------------------------------------------------*/
         /* s _ r d a
          * Обработать прерывание "доступны данные для приема"
          */

                                      - 8-27 -
         static void s_rda(void)
         {
             registr int intmask;
             char c;
         /* читать из коммуникационного порта */
             c = inp(comport);
             if(enable_xonxoff) {
                if (c == XON_ASCII) {
                      rcvd_xonxoff = XON_RCVD;
         /* Включить прерывание THRE, если оно выключено. */
                      turnon_int(THREINT,intmask);
                      return;
                }
                if(c == XOFF_ASCII) {
                      rcvd_xonxoff = XOFF_RCVD;
         /* Сбросить прерывания THRE */

                      intmask = inp(IER);
                      if (intmask & THREINT)
                                outp(IER, intmask & THREOFF);
                      return;
                 }
             }
             q_puton(rxq, &c);
         /* Проверить, заполнена ли почти очередь (75%) */
             if(enable_xonxoff) {
                if(rxq->count >= HI_TRIGGER(RXQSIZE) &&
                   sent_xonxoff != XOFF_SENT ) {
         /* Установить флаг для направления XOFF */
                   send_xoff = TRUE;
         /* Включить прерывания THRE так, чтобы послать XOFF */
                   turnon_int(THREINT,intmask);
               }
            }
         }
         /*---------------------------------------------------------*/
         /* s _ t r m t y
          * Обработать прерывание "регистр хранения передачи
          * свободен"
          */
         static void s_trmty(void)
         {
             char c;
             registr int ierval;

             if (send_xoff == TRUE) {
                 outp(comport, XOFF_ASCII);
                 send_off = FALSE;
                 sent_xonxoff = XOFF_SENT;
                 return;
             }
             if (send_xon == TRUE) {
                 outp(comport, XON_ASCII);
                 send_xon = FALSE;

                 sent_xonxoff = XON_SENT;
                 return;

                                      - 8-28 -
             }
         /* Поместить символ в регистр хранения передачи */
             if( q_getfrom(txq, &c) != NULL){
                 outp(comport, c);
                 return;
             }
         /* Нечего посылать -- сбросить прерывания THRE */
             ierval = inp(IER);
             if (ierval & THREINT) outp(IER, ierval & THREOFF);
         }
         /*---------------------------------------------------------*/
         /* s _ s e t u p
          * Установить все для связи.
          * Вернуть 1, если установка прошла успешно, в противном
          * случае вернуть 0.
          */
         int s_setup(short port_number, unsigned commparams)
         {
             int intmask;

             if (port_number < 0 || port_number > 1)
                 report_error("Неверный номер порта!\n");

         /* Получить базовый адрес последовательного порта из
          * области данных BIOS */
             comport = *(BIOS_DATA + port_number);
             if (comport == 0)
             {
                 report_error("BIOS не может найти порт!\n");
                 return(0);
             }

         /* Установить маски для программируемого контроллера
          * прерываний 8259A. Для разрешения прерывания порта
          * эта маска логически умножается с маской регистра

          * в 21h. Для запрещения, логически сложить маску
          * запрещения с маской регистра. Номер прерывания
          * определяется как 8 + уровень IRQ прерывания.
          * Коммуникационный порт 1 имеет IRQ 4, порт 2 имеет
          * IRQ 3.
          */
             if (port_number == 0)
             {
                 int_enable_mask = 0xef;
                 int_disable_mask = 0x10;
                 int_number = 12;

             }
             if (port_number == 1)
             {
                 int_enable_mask = 0xf7;
                 int_disable_mask = 8;
                 int_number = 11;
             }

         /* Получить номер старого прерывания и сохранить его. */

                                      - 8-29 -
             old_handler = _dos_getvect(int_number);

         /* Установить новый обработчик с именем s_inthndlr.
          * Запретить прерывания при смене обработчика.
          */
             _disable();
             _dos_setvect(int_number, s_inthndlr);
             _enable();

         /* Установить коммуникационные параметры */
             _bios_serialcom(_COM_INIT, port_number, commparams);

         /* Инициализировать флаги XON/XOFF */
             rcvd_xonxoff = XON_RCVD;
             if (sent_xonxoff == XOFF_SENT)
                 send_xon = TRUE;

             else
                 send_xon = FALSE;
                 send_xoff = FALSE;

         /* Включить прерывания коммуникационного порта и
          * установка 8259A.
          */
             _disable();
         /* Установить регистр управления модемом (порт = MCR) */
             outp(MCR, MCRALL);

         /* Разрешить все прерывания последовательной карты */
             outp(IER, IERALL);

         /* Считать регистр маски прерывания 8259A и записать его
          * обратно после логического умножения с _int_enable_mask.
          */
             intmask = inp(P8259_1) & int_enable_mask;
             outp(P8259_1, intmask);

             _enable();

             return(1);
         }
         /*---------------------------------------------------------*/
         /* s _ c l e a n u p
         /* Очистить после сеанса связи. Сбросить все прерывания. */
         int s_cleanup(void)
         {
             int intmask;

         /* Выключить прерывания последовательной карты */
             _disable();
         /* Первым сбросить регистр разрешения прерывания порта */
             outp(IER, IEROFF);

         /* Сбросить все биты регистра управления модемом */
             outp(MCR, MCROFF);

         /* Затем запретить распознавание контроллером 8259A

                                      - 8-30 -
          * прерываний последовательного порта.
          */
             intmask = inp(P8259 _1) | int_disable_mask;
             outp(P8259_1, intmask);

         /* Восстановить первоначальный вектор прерывания */
             _dos_setvect(int_number, old_handler);

         /* Снова разрешить прерывания */
             _enable();
         }
         /*---------------------------------------------------------*/
         /* s _ s e n d c h a r
          * Поместить символ в очередь на передачу. Вернуть 1, если
          * все в порядке, в противном случае вернуть 0.
          */
         int s_sendchar(int ch)
         {
             int retval, intmask;
             _disable();
             retval = q_puton(txq, (char *)&ch);
             _enable();
         /* Включить прерываниеTHRE в том случае, если оно выключено
          * и не получен XOFF.
          */
             if (rcvd_xonxoff != XOFF_RCVD)
                 turnon_int(THREINT,intmask);
             return(retval);
         }
         /*---------------------------------------------------------*/
         /* s _ r c v c h a r
          * Вернуть символ из очереди на прием.
          * Вернуть 1, если очередь пуста.
          */

         int s_rcvchar(void)
         {
             int ch, intmask;
         /* Если ранее был послан сигнал XOFF, то мы должны направить
          * XON.
          */
             if(enable_xonxoff)
             {
             if(rxq->count <= LO_TRIGGER(RXQSIZE) &&
                  sent_xonxoff != XON_SENT )
                  {
                     send_xon = TRUE;
                     turnon_int(THREINT,intmask);
                  }
             }
             _disable();
             if (q_getfrom(rxq, (chr *)&ch) == NULL)
             {
                 _enable();
                 return(-1);
             }
             else

                                      - 8-31 -
             {
             _enable();
             return(ch);
             }
         }
         /*--------------------------------------------------------*/
         /* q _ g e t f r o m
          * Копировать следующий элемент данных в очередь в
          * определенное местоположение. Также вернуть указатель
          * на этот элемент.
          */
         char *q_getfrom( QTYPE *queue, char *data)
         {
             char *current;
             current = NULL;

             if(queue->front == -1) return(current);
         /* В противном случае искать данные */
             current = &(queue->data[queue->front]);
             *data = *current;
             queue->count--;
             if(queue->count == 0)
             {
         /* Очередь пуста. Сбросить начало, конец и счет */

                queue->front = queue->rear = -1;
                return(current);
             }
         /* Увеличить начальный индекс и проверить на циклический
          * переход.
          */
             if(queue->front == queue->maxsize-1)
                queue->front = 0;
             else
                queue->front++;
             return(current);
         }
         /*--------------------------------------------------------*/
         /* q _ p u t o n
          * Поместить элемент данных в очередь.
          */
         int q_puton(QTYPE *queue, char *data)
         {
         /* Сначала проверить степень заполнения очереди.
          * Если она полна, то вернуть 0.
          */
             if(queue->count == queue->maxsize) return(0);
         /* В противном случае установить на конец и провести
          * проверку на циклический переход.
          */
             if(queue->rear == queue->maxsize-1)
                queue->rear = 0;
             else
                queue->rear++;
         /* Сохранить символ в очереди */
             queue->data[queue->rear] = *data;
             queue->count++;

                                      - 8-32 -
             if(queue->front == -1) queue->front = 0;
             return(1); /* Успешно вставленный элемент */
         }
         _____________________________________________________________


                                Заключение

             В этой  главе  рассмотрены  характеристики аппаратных средств
         последовательного порта в системах MS-DOS и  представлены  методы
         его программирования. Также содержится небольшая коммуникационная
         программа,  написанная на Microsoft C 5.0, для иллюстрации реали-
         зации этих методов на практике. Программирование последовательно-
         го порта для  управляемого  прерывания  ввода/вывода  выполняется
         следующим образом:

             1. Получить  базовый адрес выбранного коммуникационного порта
                из области данных BIOS на сегменте 40h и со смещением 0.
             2. Используя функцию MS-DOS 35h,  получить адрес старой  под-
                программы обслуживания прерывания  для  номера  прерывания,
                соответствующего данному адаптеру, и сохранить его.
             3. Используя функцию MS-DOS 25h, установить для номера преры-
                вания  Вашу собственную подпрограмму обслуживания прерыва-
                ния.
             4. С  использованием функции BIOS 14h установить коммуникаци-
                онные параметры адаптера.
             5. Установить очереди приема и передачи для содержания входя-
                щих и исходящих символов.
             6. Включить в регистре управления модемом необходимые сигналы
                (например,  DTR - терминал готов и RTS - запрос на переда-
                чу).
             7. Разрешить все прерывания адаптера (установкой битов с 0 по
                3 в регистре разрешения прерывания в 1).
             8. Так же включить бит OUT2 в регистре управления модемом для
                разрешения прерываний последовательного адаптера.
             9. Запрограммировать контроллер 8259A для распознавания  пре-
                рываний с IRQ этого адаптера (путем установки соответству-
                ющего бита регистра маски прерывания, доступного через ад-
                рес порта 21h, в 0).

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

             1. Выключить прерывания последовательного адаптера.
             2. Сбросить биты регистра управления модемом.
             3. Восстановить старую подпрограмму обработки прерывания.
             4. Замаскировать прерывания для этого IRQ в 8259A.






                        Глава 9. ПРОГРАММИРОВАНИЕ EGA И VGA



                        Мониторы и возможности EGA
                        Прямая запись на экран
                        Много точек
                        Чтение битовых матриц
                        Цветовые палитры EGA
                        Регистр циклического сдвига данных
                        Режим отображения 256 цветов VGA
                        Заключение





             Усовершенствованный графический адаптер (EGA) и  более  новая
         видеографическая матрица (VGA) предлагают разработчику единствен-
         ные в своем роде задачи.  EGA становится традиционной графической
         картой в мире MS-DOS.  Тем не менее, существует четыре совершенно
         различных графических стандарта поддержки EGA:

             1. Графические режимы, совместимые с CGA.
             2. Два  новых графических режима EGA для 200-строчных цветных
                мониторов.
             3. Новый графический режим EGA для 350-строчных цветных мони-
                торов.
             4. Новый  графический режим EGA для использования с монохром-
                ными (текстовыми) мониторами.

             Помимо всех указанных режимов, VGA поддерживает еще несколько
         новых.  В  этой  главе рассматриваются концепции программирования
         каждого нового графического режима EGA, а также новые режимы VGA.
         Не охватываются совместимые с CGA текстовый и графический режимы,
         так как они более приемлемы для карты CGA.
             Оригинальная плата  EGA фирмы IBM поступает с 64К графической
         памяти.  Она может быть расширена до 256К. Чем больше память EGA,
         тем шире графические возможности адаптера.  EGA-совместимые карты
         других производителей обычно поступают с уже  установленными 256К
         памяти.  Карты  VGA  в большинстве новых персональных компьютерах
         IBM System 2 включены в объединительную плату;  для других ПК VGA
         доступна в качестве платы расширения.  В каждом случае VGA всегда
         будет иметь установленными 256К памяти.
             Видеофункции  компьютера  IBM  PC вызываются прерыванием BIOS
         10h. Эти видеофункции позволяют программе устанавливают текстовый
         или  графический  режимы,  считывать  или  писать одиночные точки
         растра и помещать символы на экран. Адаптер EGA имеет новую BIOS,
         которая заменяет все оригинальные функции персонального компьюте-
         ра и добавляет несколько новых. Эти функции EGA позволяют опреде-
         лять новые символы,  лучше управлять палитрой и выводом на печать
         текстовых строк.


                                      - 9-2 -

                            Мониторы и возможности EGA

             Адаптер EGA создан для работы с тремя различными типами мони-
         торов: цветным дисплеем IBM, усовершенствованным цветным дисплеем
         IBM или монохромным дисплеем IBM,  а также с эквивалентными дисп-
         леями других производителей.  Обычно,  тип используемого монитора
         определяет  графическое разрешение,  максимальное количество цве-
         тов, цветовую палитру и число элементов растра, составляющее каж-
         дый символ.  Карта VGA должна использоваться с цветным или монох-
         ромным аналоговым монитором.   Несмотря  на  то,  что  аналоговый
         монитор функционально отличается от цифрового (или ТТЛ) монитора,
         программист должен рассматривать его как цифровой монитор с высо-
         кой разрешающей способность.  Следует только обратить внимание на
         то, цветной он или монохромный.
             Цветной монитор  IBM  имеет  максимальную разрешающую способ-
         ность 640 х 200 элементов изображения.  Цветной монитор ограничен
         200  вертикальными  строками  развертки ввиду того,  что он может
         применять только одну вертикальную частоту сканирования.  При ис-
         пользовании  с  цветным  монитором адаптер EGA совместим со всеми
         текстовымы и графическими режимами цветного графического  адапте-
         ра.  Два новых графических режима,  режимы 13 и 14, используют до
         16 цветов с разрешением 320 х 200 и 640  х  200.  Тем  не  менее,
         цветной  монитор ограничивается 16-цветной фиксированной палитрой
         и 200-ми вертикальными линиями развертки.  Фиксированная  палитра
         использует те же 16 цветов, что и CGA в текстовом режиме. Исполь-
         зуемый по умолчанию прямоугольник  для  генерации  символа  имеет
         размер 8 х 8 элементов изображения. Эти режимы перечислены в таб-
         лице 9-1.
                                                         Таблица 9-1
                     Использование EGA c цветным монитором IBM
         ДДДДВДДДДДДДДДВДДДДДДДВДДДДДДДДВДДДДДДДВДДДДДДДДВДДДДВДДДДДДДД
         Но- ¦ Тип     ¦Макси- ¦ Размер ¦ Размер¦ Макси- ¦Сег-¦ Разре-
         мер ¦         ¦мальное¦(столбцы¦ прямо-¦ мальное¦мент¦ шение
         ре- ¦         ¦число  ¦ х      ¦ уголь-¦ число  ¦бу- ¦
         жима¦         ¦цветов ¦ строки)¦ ника  ¦ страниц¦фера¦
         ДДДД†ДДДДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДД†ДДДДДДДД
          0  ¦ Текст   ¦  16   ¦ 40х25  ¦  8х8  ¦ 8      ¦B800¦ 320х200
          1  ¦ Текст   ¦  16   ¦ 40х25  ¦  8х8  ¦ 8      ¦B800¦ 320х200
          2  ¦ Текст   ¦  16   ¦ 80х25  ¦  8х8  ¦ 4/8/8* ¦B800¦ 640х200
          3  ¦ Текст   ¦  16   ¦ 80х25  ¦  8х8  ¦ 4/8/8* ¦B800¦ 640х200
          4  ¦ Графика ¦   4   ¦ 40х25  ¦  8х8  ¦ 1      ¦B800¦ 320х200
          5  ¦ Графика ¦   4   ¦ 40х25  ¦  8х8  ¦ 1      ¦B800¦ 320х200
          6  ¦ Графика ¦   2   ¦ 80х25  ¦  8х8  ¦ 1      ¦B800¦ 640х200
         13  ¦ Графика ¦  16   ¦ 40х25  ¦  8х8  ¦ 2/4/8* ¦A000¦ 320х200
         14  ¦ Графика ¦  16   ¦ 80х25  ¦  8х8  ¦ 1/2/4* ¦A000¦ 640х200
         ДДДДБДДДДДДДДДБДДДДДДДБДДДДДДДДБДДДДДДДБДДДДДДДДБДДДДБДДДДДДДД
         (*) - Зависит от объема установленной памяти EGA


                      Усовершенствованный графический дисплей

             Усовершенствованный графический дисплей фирмы  IBM  совместим
         со  всеми режимами цветного дисплея и использует еще один режим с
         высокой  разрешающей  способностью.  Усовершенствованный  цветной
         дисплей способен применять две вертикальные частоты сканирования,
         одну для 200-строчных режимов и одну  для  350-строчных  режимов.

                                      - 9-3 -
         Кроме двух стандартных вертикальных частот сканирования,  генери-
         руемых EGA,  мониторы с мультисинхронизацией могут использовать и
         более  высокие  частоты  для  получения более высокой разрешающей
         способности.  Режим высокого разрешения - режим 16,  может приме-
         няться только с усовершенствованным цветным дисплеем IBM, эквива-
         лентным монитором или монитором с мультисинхронизацией,  так  как
         вертикальное разрешение имеет значение 350 строк, а цветной дисп-
         лей может отображать только 200 строк.
             При использовании  с  усовершенствованным цветным монитором в
         большинстве режимов EGA может отображать 16 цветов  из 64-цветной
         палитры. 16 цветов доступны только в режиме 16 в том случае, если
         карта EGA имеет более 64К памяти.  Режимы с 4 по  6,  графические
         режимы,  совместимые с CGA, ограничены той же 16-цветной фиксиро-
         ванной палитрой, как и CGA. Текстовые режимы усовершенствованного
         цветного дисплея используют 8х14 элементов изображения для каждого
         символа, что дает символ с более высокой разрешающей способностью,
         чем символ, генерируемый в режиме CGA. Режимы усовершенствованного
         цветного  дисплея  (и  мультисинхронизационных эквивалентов) пере-
         числены в таблице 9-2.

                                                         Таблица 9-2
                      Использование EGA c усовершенствованным
               монитором IBM (или монитором с мультисинхронизацией)
         ДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДВДДДДДДДДВДДДДДВДДДДДДД
         Но- ¦ Тип    ¦ Макси- ¦ Размер ¦ Размер¦ Макси- ¦Сег- ¦Разре-
         мер ¦        ¦ мальное¦(столбцы¦ прямо-¦ мальное¦мент ¦шение
         ре- ¦        ¦ число  ¦ х      ¦ уголь-¦ число  ¦бу-  ¦
         жима¦        ¦ цветов ¦ строки)¦ ника  ¦ страниц¦фера ¦
         ДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДДД†ДДДДДДД
          0  ¦ Текст  ¦   16   ¦ 40х25  ¦ 8х14  ¦ 8      ¦B800 ¦320х350
             ¦        ¦ из 64  ¦        ¦       ¦        ¦     ¦
          1  ¦ Текст  ¦   16   ¦ 40х25  ¦ 8х14  ¦ 8      ¦B800 ¦320х350
             ¦        ¦ из 64  ¦        ¦       ¦        ¦     ¦
          2  ¦ Текст  ¦   16   ¦ 80х25  ¦ 8х14  ¦ 4/8/8* ¦B800 ¦640х350
             ¦        ¦ из 64  ¦        ¦       ¦        ¦     ¦
          3  ¦ Текст  ¦   16   ¦ 80х25  ¦ 8х14  ¦ 4/8/8* ¦B800 ¦640х350
             ¦        ¦ из 64  ¦        ¦       ¦        ¦     ¦
          4  ¦ Графика¦    4   ¦ 40х25  ¦ 8х8   ¦ 1      ¦B800 ¦320х200
          5  ¦ Графика¦    4   ¦ 40х25  ¦ 8х8   ¦ 1      ¦B800 ¦320х200
          6  ¦ Графика¦    2   ¦ 80х25  ¦ 8х8   ¦ 1      ¦B800 ¦640х200
         13  ¦ Графика¦   16   ¦ 40х25  ¦ 8х8   ¦ 2/4/8* ¦A000 ¦320х200
             ¦        ¦ из 64  ¦        ¦       ¦        ¦     ¦
         14  ¦ Графика¦   16   ¦ 80х25  ¦ 8х8   ¦ 1/2/4* ¦A000 ¦640х200
             ¦        ¦ из 64  ¦        ¦       ¦        ¦     ¦
         16  ¦ Графика¦  4/16  ¦ 80х25  ¦ 8х14  ¦ 1/2*   ¦A000 ¦640х350
             ¦        ¦ из 64* ¦        ¦       ¦        ¦     ¦
         ДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДБДДДДДДДДБДДДДДБДДДДДДД
         (*) - Зависит от объема установленной памяти EGA.

                          Монохромные графические режимы

             Монохромный дисплей  IBM используется в основном только в ка-
         честве текстового дисплея.  Текстовый режим совместим с монохром-
         ным адаптером IBM. Тем не менее, имеется новый режим, который до-
         бавляет графику с разрешающей  способностью  640х350  и  четырьмя
         "цветами": черным, видимым, мерцающим видимым и интенсивным види-
         мым.  Если монохромный монитор подключен к EGA,  то EGA не  может

                                      - 9-4 -
         использовать  ни  один  из своих цветных графических режимов,  но
         способна применять новый монохромный графический режим.
             EGA преобразует шрифт 8х14,  используемый с расширенным цвет-
         ным монитором в MDA-совместимый шрифт 9х14.  Это достигается рас-
         ширением любых символов,  формирующих строки,  в позицию девятого
         элемента изображения.  Не путайте подключение EGA к  монохромному
         монитору  с подключением VGA к аналоговому монохромному монитору.
         VGA в этом случае трактуется как подключенная к цветному монитору
         с высоким разрешением или к цифровому монохромному монитору в за-
         висимости от установки переключателей VGA.
             Есть небольшое изменение в стандартном MDA режиме 7,  тексто-
         вом  режиме,  с EGA.  Это изменение заключается в добавлении нес-
         кольких видеостраниц. Оригинальный монохромный адаптер использует
         только одну страницу. EGA может хранить до 8 отдельных видеостра-
         ниц в  зависимости от объема памяти EGA.  Номер страницы определя-
         ется в регистре ВН микропроцессора 80х86 при использовании тексто-
         вых функций BIOS.  Более старое программное обеспечение использует
         регистр ВН для других данных или терпит неудачу  при  попытке  его
         инициализации,  поэтому  окончательный текст может не появиться на
         нужной странице.
             EGA-совместимые карты прочих производителей  могут предложить
         Hercules-совместимый  графический  режим при использовании  моно-
         хромного дисплея. Два режима для монохромного дисплея приведены в
         таблице 9-3.

                                                         Таблица 9-3
                     Использование EGA c монохромным монитором
         ДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДВДДДДДДДДВДДДДДВДДДДДДД
         Но- ¦ Тип    ¦ Макси- ¦ Размер ¦ Размер¦ Макси- ¦Сег- ¦Разре-
         мер ¦        ¦ мальное¦(столбцы¦ прямо-¦ мальное¦мент ¦шение
         ре- ¦        ¦ число  ¦ х      ¦ уголь-¦ число  ¦бу-  ¦
         жима¦        ¦ цветов ¦ строки)¦ ника  ¦ страниц¦фера ¦
         ДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДДД†ДДДДДДД
          7  ¦ Текст  ¦   4    ¦ 80х25  ¦ 9х14  ¦ 4/8*   ¦B000 ¦720х350
         15  ¦ Графика¦   4    ¦ 80х25  ¦ 8х14  ¦ 1/2*   ¦A000 ¦640х350
         ДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДБДДДДДДДДБДДДДДБДДДДДДД
         (*) - Зависит от объема установленной памяти EGA

             Для использования с VGA IBM  предоставляет  как  монохромные,
         так и цветные аналоговые мониторы. VGA можно установить таким об-
         разом,  что она может трактовать аналоговый монитор как  цифровой
         монохромный  монитор  или  как цифровой цветной монитор.  Другими
         словами, VGA может быть установлена так, что она может трактовать
         цветной  аналоговый  монитор как цифровой монохромный монитор,  и
         монохромный аналоговый монитор как цифровой монохромный монитор с
         высокой разрешающей способностью.  Эта возможность существует для
         обратной совместимости с EGA и,  при установке для работы  в  ка-
         честве EGA с монохромным монитором, свойства VGA не отличаются от
         свойств EGA с подключенным монохромным текстовым монитором.
             Программа самотестирования  при включении питания настраивает
         карту VGA для работы после загрузки  персонального  компьютера  в
         качестве либо монохромного адаптера, либо цветного адаптера. Пос-
         ле установки в качестве монохромного адаптера доступны только ви-
         деорежимы, перечисленные в таблице 9-3. После установки в качест-
         ве цветного адаптера доступны видеорежимы, указанные в табл. 9-4.


                                      - 9-5 -
                                                         Таблица 9-4
                             Аналоговый монитор и VGA,
                       конфигурированная для цветного режима
         ДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДВДДДДДДДДВДДДДВДДДДДДДД
         Но- ¦        ¦ Макси- ¦ Размер ¦ Размер¦ Макси- ¦Сег-¦ Разре-
         мер ¦   Тип  ¦ мальное¦(столбцы¦ прямо-¦ мальное¦мент¦ шение
         ре- ¦        ¦ число  ¦ х      ¦ уголь-¦ число  ¦бу- ¦
         жима¦        ¦ цветов ¦ строки)¦ ника  ¦ страниц¦фера¦
         ДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДД†ДДДДДДДД
          0  ¦ Текст  ¦   16   ¦ 40х25  ¦ 9х16  ¦   8    ¦B800¦ 360х400
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
          1  ¦ Текст  ¦   16   ¦ 40х25  ¦ 9х16  ¦   8    ¦B800¦ 360х400
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
          2  ¦ Текст  ¦   16   ¦ 80х25  ¦ 9х16  ¦   8    ¦B800¦ 720х400
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
          3  ¦ Текст  ¦   16   ¦ 80х25  ¦ 9х16  ¦   8    ¦B800¦ 720х400
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
          4  ¦ Графика¦    4   ¦ 40х25  ¦ 8х8   ¦   1    ¦B800¦ 320х200
          5  ¦ Графика¦    4   ¦ 40х25  ¦ 8х8   ¦   1    ¦B800¦ 320х200
          6  ¦ Графика¦    2   ¦ 80х25  ¦ 8х8   ¦   1    ¦B800¦ 640х200
         13  ¦ Графика¦   16   ¦ 40х25  ¦ 8х8   ¦   8    ¦A000¦ 320х200
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
         14  ¦ Графика¦   16   ¦ 80х25  ¦ 8х8   ¦   4    ¦A000¦ 640х200
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
         16  ¦ Графика¦   16   ¦ 80х25  ¦ 8х14  ¦   2    ¦A000¦ 640х350
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
         17  ¦ Графика¦    2   ¦ 80х30  ¦ 8х16  ¦   1    ¦A000¦ 640х480
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
         18  ¦ Графика¦   16   ¦ 80х30  ¦ 8х16  ¦   1    ¦A000¦ 640х480
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
         19  ¦ Графика¦   256  ¦ 40х25  ¦ 8х8   ¦   1    ¦A000¦ 320х200
             ¦        ¦из 256К ¦        ¦       ¦        ¦    ¦
         ДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДБДДДДДДДДБДДДДБДДДДДДДД

                    Соображения по установке и проверка наличия

             Возможности EGA зависят от типа монитора и объема  памяти  на
         плате EGA. Тип монитора определяет, какой видеорежим использовать
         для графики или текста,  а объем памяти EGA определяет число дос-
         тупных  цветов и страниц.  Для Ваших программ очень важно узнать,
         имеется ли в персональном компьютере в наличии EGA,  до того, как
         Вы попытаетесь ее использовать, а также тип применяемого монитора
         и объем доступной памяти.  Это делает  программа,  приведенная  в
         листинге 9-1.  Для указания на структуру с информацией по EGA вы-
         зывается функция get_ega_info(&info).  Байт 0х40:0х87 преобразует
         информацию о конфигурации EGA, памяти и мониторе. Этот байт явля-
         ется одним из байтов состояния,  которые EGA  BIOS  содержит  для
         внутреннего  использования  и обеспечения программ необходимой им
         информацией.
            Мы интересуемся битами 5 и 6, которые показывают полную память
         EGA;  битом 3, который показывает, является ли EGA активным дисп-
         леем; и битом 1, который указывает тип используемого монитора.
             Функция также  вызывает один из новых вызовов BIOS EGA,  аль-
         тернативную функцию 10,  которая возвращает информацию  EGA.  EGA
         вызывается  помещением  0х12 в регистр АН и 0х10 в регистр BL,  а
         также использованием прерывания 10h. Вызов EGA BIOS содержит сле-
         дующую информацию:

                                      - 9-6 -

                           Возвращаемая информация о EGA

             Результат:  Int 0x10
             Вызывается: AH = 0х12
                         для выбора альтернативных функций EGA
                         BL = 0x10
                         Альтернативная функция для информации EGA
             Возвращает: BH =  0 = Цветной монитор
                               1 = Монохромный монитор
                         BL =  Расшифрованная память EGA:
                               0 = 64К
                               1 = 128К
                               2 = 192К
                               3 = 256К
                         СН =  Биты признаков
                         CL =  Установки переключателей платы EGA

             Так как BIOS персонального компьютера  не  использует  видео-
         функцию  0х12,  этот вызов может быть использован в качестве про-
         верки наличия EGA.  BIOS персонального компьютера спокойно откло-
         нит   неизвестное  ей  прерывание  10h  и  не  изменит  состояния
         регистров.  Таким образом,  если выходящие регистры не изменяются
         вызовом или входящие регистры не соответствуют данным о байте ин-
         формации EGA, значит EGA отсутствует. Если EGA имеется в наличии,
         то  тип используемого монитора определяется считыванием положения
         установочных переключателей.  Вы предполагаете,  что пользователь
         правильно  установил  переключатели  и их положение соответствует
         типу монитора.
             После обнаружения  EGA  функция проведет тестирование системы
         на наличие карты VGA. Большинство регистров EGA только записывае-
         мые,  в то время как в VGA они являются считываемыми/записываемы-
         ми. Регистр устанавливается в определенное значение и затем дела-
         ется  попытка  считать  это  значение.  Если  считанный  байт  не
         соответствует записанному,  то имеющаяся карта - EGA, в противном
         случае - VGA. Используемый регистр является регистром маски бита,
         который далее будет детально рассмотрен.
             Программа EGACHECK.C,  приведенная в листинге 9-1,  выполняет
         проверку активной карты EGA. (В системе может присутствовать дру-
         гая дисплейная карта. Если активна другая карта, то бит 3 байта
         0х40:0х87 будет иметь значение 1). Если обнаружена активная карта
         EGA, то сохраняется информация об установке.
             Макрокоманда PEEK_BYTE(seg,off),  представленная  в  листинге
         9-1, позволяет выбирать байт из любого места памяти персонального
         компьютера.  Макрокоманда работает путем сдвига значения сегмента
         влево  на  одно слово (16 бит) с последующим логическим сложением
         бита смещения для формирования длинного прерывания.  Это прерыва-
         ние затем сбрасывается в указатель far.

                         Листинг 9-1. Программа EGACHECK.C

         ----------------------------------------------------------------

         /* egacheck.c */
         /* Проверяет наличие карты EGA или VGA */
         /* При нахождении одной из них, информация сохраняется */
         #include <conio.h>

                                      - 9-7 -
         #include <dos.h>
         #include <stdio.h>

         #define PEEK_BYTE(seg,off) \
              (*(char far *) ( (long)(seg)<<16 | (off) ) )

         struct Ega_info     /* для хранения информации о EGA */
         {
           char card ;       /* для хранения типа карты */
           char monitor ;    /* для хранения типа монитора */
           int memory ;      /* объем памяти: 64, 128, 192, 256К */
           char high_res_graphics ;
           char text_mode ;
         }  ;

         int get_ega_info(struct Ega_info *) ;

         main()
         {
         struct Ega_info info ;

         if(get_ega_info(&info))   /* тест на наличие EGA */
          {
          if(info.card == 'E')
            {
            printf("\n\nИспользуется EGA.") ;
            printf("\nПодключена к") ;
            switch(info.monit)
            {
              case 'C': puts(" цветному монитору") ;
                        break ;
              case 'M': puts(" монохромному монитору") ;
                        break ;
              case 'H': puts("усовершенствованному цветному монитору");
                        break ;
              default:  break ;  /* не определен */
            }
            printf("n\%iK байт памяти EGA.", info.memory);
            }
            else
               printf("\n\nИспользуется VGA.") ;
            printf("\nРежим %#2i - графика с высоким разрешением.",
                  (int)info.high_res_graphics) ;
            printf("\nРежим %#2i - текстовый режим.\n\n",
                  (int)info.text_mode) ;
          }
         else
          puts("\nНет активного EGA.") ;
         }                               /* конец main() */


         int get_ega_info(info)
         struct Ega_info *info ;

         /* Эта функция проверяет, есть ли в системе активный EGA */
         {
         union REGS regs ;

                                      - 9-8 -
         int i, test_mask = 1 ;
         /* Принять байт информации EGA из области данных BIOS */
            char bios_info = PEEK_BYTE(0x40,0x87) ;

            /* Бит 3 показывает, активный EGA или нет
             * если нет, то проверить наличие */
            if(bios_info & 0x8)
               return (0) ; /* если бит 3 = 1, EGA не активный */

            regs.h.ah = 0x12 ; /* альтернативная функция BIOS EGA */
            regs.h.bl = 0x10 ; /* получить информацию */
            regs.h.bh = 0xFF ; /* не возможное значение */
            int86(0x10, &regs, &regs) ; /* видео вызов EGA BIOS */

         /* bios_info биты 5 + 6 и BL(расшифрованная память EGA) и */
         /* bios_info бит 1 и ВН должны быть равны для EGA */
         if((regs.h.bl != ((bios_info & 0x60) >> 5 || /* память */
            (regs.h.bh != ((bios_info & 0x2) >> 1  || /* монитор */
            (regs.h.bh == 0xFF))            /* ВН должен изменить*/
           /* EGA есть, сохранить тип монитора */
           /* Код тип монитора:
                'C' для цветного,
                'M' для монохромного,
                'H' для hercules */
          switch(regs.h.cl) /* cl имеет установку переключателей EGA */
          {
             case 0: /* первоначально моно, EGA цветной 40х25 */
             case 6: /* моно второй, EGA цветной 40х25 */
                    info->monitor = 'C' ;
                    info->high_res_graphics = 0xD ;
                    info->text_mode = 0x1 ;
                    break ;
             case 1:  /* первоначально моно, EGA цветной 80х25 */
             case 2:  /* то же, что 1 */
             case 7:  /* моно второй, EGA цветной 80х25 */
             case 8:  /* то же, что 7 */
                    info->monitor = 'C' ;
                    info->high_res_graphics = 0xE ;
                    info->text_mode = 0x3 ;
                    break ;
             case 3: /* первоначально моно, EGA с высоким разрешением */
             case 9: /* сначала EGA с высоким разрешением, потом моно */
                    info->monitor = 'H' ;
                    info->high_res_graphics = 0x10 ;
                    info->text_mode = 0x3 ;
                    break ;
             case 4:  /* сначала 40 цветной, EGA моно */
             case 5:  /* сначала 80 цветной, EGA моно */
             case 10: /* сначала EGA моно, затем 40 цветной */
             case 11: /* сначала EGA моно, затем 80 цветной */
                    info->monitor = 'M' ;
                    info->high_res_graphics = 0xF ;
                    info->text_mode = 0x7 ;
                    break ;
             default: /* зарезервированные установки переключателей */
             return (0) ;
          }

                                      - 9-9 -
          info->memory = 64 * (regs.h.bl + 1) ;

          /* Различить EGA и VGA: */
          /* Это выполняется посредством записи значения в только
             считываемый регистр в EGA, но считываемый/записываемый
             в VGA */
         outp(0x3CE, 8) ;         /* маска бита EGA/VGA */
         outp(0x3CF, test_mask) ; /* направить значение теста */
         outp(0x3CE, 8) ;         /* снова маска бита */
         if(inp(0x3CF) == test_mask)
            {
            info->card = 'V' ;    /* регистр можно считать */
            if(info->monitor != 'M')
               {
               info->high_res_graphics = 0x12 ;
               info->text_mode = 0x3 ;
               }
            /* Если подключено к монохромному, то значения уже
             * установлены.
             */
            }
         else
            info->card = 'E' ; /* EGA */
         outp(0x3CE, 8) ;      /* сбросить маску бита */
         outp(0x3CF, 0xFF) ;
         /* В этой системе активен EGA/VGA, вернуть память */
         return(info->memory) ;
         }
         ---------------------------------------------------------------

             В новый заглавный файл, названный ega.h, должны быть добавле-
         ны  прототип  функции get_ega_info() и скелет структуры Ega_info.
         Эти функция и скелет будут использованы в последующих примерах.
             Теперь мы знаем,  какой режим надо использовать для графики и
         можем изобразить что-нибудь на дисплее.  EGA BIOS,  также  как  и
         BIOS  персонального компьютера имеет вызов Write Dot (писать точ-
         ку).  Этот вызов работает медленно, но очень полезен во всех гра-
         фических картах IBM. Вот некоторые характеристики вызова EGA BIOS
         Write Dot:

             Write Dot

             Результат:  Int 0x10

             Вызывается: AН =  0хС для выбора функции Write Dot
                         BH =  Страница
                         DX =  Номер строки
                         СХ =  Номер столбца
                         AL =  Значение цвета

             Возвращает: Ничего

             Обратите внимание на добавление в ВН номера страницы. Если Вы
         преобразуете  старое программное обеспечение для работы с EGA, то
         убедитесь, что номер страницы находится в ВН перед вызовом преры-
         вания  10h.  Программы,  написанные для монохромного адаптера или

                                      - 9-10 -
         CGA в графическом режиме,  наиболее чувствительны к этому  недос-
         татку.
             Вызов BIOS для переключения в графический режим  точно  такой
         же,  как и функция 0 прерывания 10h персонального компьютера. Тем
         не менее, BIOS не выполняет проверку на предмет того, не повредит
         ли выбранный Вами режим Ваш монитор. Монохромный монитор, подклю-
         ченный к EGA, может быть поврежден сигналом режима цветного текс-
         та или графики,  поэтому важно проверить совместимость монитора и
         режима работы.  В программе 9-1 функция  get_ega_info(&info)  ис-
         пользуется  для  проверки монитора и нахождения безопасного в ис-
         пользовании режима работы  с  высокой  разрешающей  способностью.
         Программа, представленная в листинге 9-2, демонстрирует использо-
         вание функции set_crt_mode() для установки графического  режима и
         использование  dot(),  которая  применяет функцию BIOS Write Dot.
         Эта программа начертит серию параллельных диагональных линий.

                         Листинг 9-2. Программа DIAGONAL.C
         ---------------------------------------------------------------

         /* diagonal.c */
         /* Демонстрирует графический режим с высоким разрешением */
         #include <conio.h>
         #include <dos.h>
         #include <stdio.h>

         void set_crt_mode( char ) ;   /* добавить это в "ega.h" */
         void dot( int, int, int, int ) ;

         main()
         {
           registr i,j ;
           struct Ega_info info ;
           if(get_ega_info(&info))
              set_crt_mode(info.high_res_graphics);
           else
              return(1) ;

           for(j = 0; j <= 500; j += 5)
           for(i = 0; i <= 100; ++i)
               dot(i,i+j,13,0) ;
           getch() ;   /* ожидать символ, который надо изобразить */
           set_crt_mode(info.text_mode) ;
           return(0) ;
         }

         /*==========================================================*/
         void dot(row,col,color,page)
         int row, col, color, page;
         {
            union REGS regs ;
            regs.x.dx = row ;
            regs.x.cx = col ;
            regs.h.al = (char)color ;
            regs.h.ah = (char)0xC ;      /* Вызов Write Dot */
            regs.h.bh = (char)page ;
            int86(0x10, &regs, &regs) ;
         }

                                      - 9-11 -

         /*==========================================================*/
         void set_crt_mode(mode)
         char mode ;
         {
           union REGS regs ;
           regs.h.al = mode ;         /* al = установить режим */
           regs.h.ah = (char)0 ;      /* Функция "установить режим" */
           int86(0x10, &regs, &regs); /*Выполнить прерывание BIOS 10h*/
         }
         /*==========================================================*/

         ---------------------------------------------------------------


             Как только Вы увидите,  как медленно  работает  функция  BIOS
         Write Dot,  то,  вероятно, спросите, можно ли ускорить ее работу.
         Реализация этого требует обхода EGA BIOS  и  помещения  элементов
         изображения непосредственно в память EGA.  Тем не менее,  сначала
         Вы должны понять,  каким образом организована память EGA,  и  то,
         как можно ею управлять.


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

             Для графики EGA использует два различных  способа организации
         памяти дисплея.  В режимах с 4 по 6 EGA использует такую же орга-
         низацию памяти,  что и CGA. В этих режимах сегмент памяти дисплея
         начинается в 0хВ800 и использует 80 битов на каждую строку скани-
         рования (линию развертки). Так как линий развертки 200, использу-
         ется  16,000  байтов.  В  режиме  средней разрешающей способности
         320х200, каждый  байт представляет 4 элемента изображения с одним
         из четырех цветов,  или два бита на элемент изображения. В режиме
         6 каждый байт представляет 8 элементов изображения с двумя цвета-
         ми,  или один бит на элемент изображения. Если бит имеет значение
         0,  то соответствующий элемент изображения выключен.  Кроме того,
         четные линии развертки расположены в первых 8К дисплейной памяти,
         а  нечетные  занимают вторые 8К памяти.  Разделенная память линий
         развертки требует тестирования каждого смещения элемента  изобра-
         жения для определения того, четное оно или нечетное.
             Дисплейная память  режимов с 13 по 16 (по режим 18 VGA) начи-
         нается с сегмента 0хA000 и использует до 64К адресного пространс-
         тва  центрального  процессора  80х86.  Каждый байт представляет 8
         элементов изображения,  причем самый значимый бит расположен пер-
         вым слева.  Линии развертки не разделены в памяти,  как в режимах
         CGA, поэтому смещение байта элемента изображения проще в вычисле-
         нии.  В  режиме 16 EGA имеет максимальную разрешающую способность
         640 х 350,  или 224,000 элементов изображения. Так как имеется до
         16 цветов,  каждый элемент изображения должен использовать 4 бита
         для определения цвета.  Все вместе это  составляет  использование
         общей памяти, равной (640 х 350 элементов изображения / 8 элемен-
         тов изображения на байт х 4 бита на элемент изображения) 109К.
             Центральный процессор   80х86,   применяемый  в  персональном
         компьютере РС,  может адресовываться только  к  сегменту  объемом
         64К.  EGA приспосабливается к 64К сегментному пределу путем деле-
         ния 128К из ее 256К памяти на четыре битовые матрицы объемом 32К.
         Каждая  битовая матрица (или битовый массив) соответствуют одному

                                      - 9-12 -
         биту цвета элемента изображения.  Представьте,  что  эти  битовые
         матрицы  сложены друг на друга по одному адресу центрального про-
         цессора.  Каждый  адрес  дисплейной  памяти реально  представляет
         4 байта памяти EGA.
             В режимах VGA 17 и 18 организация памяти EGA просто  расширя-
         ется на следующие 130 линий развертки. VGA имеет режим разрешения
         320 х 200 с 256 цветами.  Позже этот режим будет  рассмотрен  от-
         дельно.

                                 Регистры-защелки

             Чтение или запись 4 различных байтов (одного для каждой бито-
         вой матрицы) по одному адресу  центрального  процессора  является
         проблемой. Для ее преодоления EGA имеет четыре  регистра-защелки.
         Регистры-защелки EGA временно сохраняют 1 байт из каждого из  че-
         тырех  битовых  матриц.  Логическая схема EGA заполняет каждый из
         четырех регистров-защелок байтом каждой битовой матрицы по  адре-
         су, который центральный процессор считал последним.  Когда  цент-
         ральный  процессор посылает байт по последнему считанному адресу,
         содержание каждого из четырех регистров может  остаться  неизмен-
         ным,  модифицироваться  или быть полностью заменено данными цент-
         рального процессора.  Содержание регистра-защелки  затем  пишется
         обратно в каждую из битовых матриц EGA.
             После обратной  записи содержания регистров-защелок в битовые
         матрицы EGA,  регистры снова "складируются",  сохранив  один  бит
         каждого  из  четырех  байтов,  формируя 4-битовый цвет для восьми
         элементов изображения. Взаимоотношения между  регистрами-защелка-
         ми и битовыми матрицами проиллюстрированы на рисунке 9-1, который
         демонстрирует состояние памяти EGA и содержание четырех регистров
         -защелок после считывания центральным процессором байта по адресу
         А000:0000.  Восемь элементов изображения в байте содержат цвета с
         0 по 7.
             Важно понять,  что байт, возвращенный центральным процессором
         после чтения A000:0000, не используется. Этот байт только читает-
         ся для установления того,  с какими элементами изображения  рабо-
         тать (в данном случае,  элементы изображения с 0 по 7 в строке 0)
         и для "подготовки" регистров-защелок,  позволяющих  данным  цент-
         рального процессора  манипулировать  отдельными  байтами  битовых
         матриц.   Это  позволяет  центральному  процессору  персонального
         компьютера модифицировать,  заменять или очищать восемь элементов
         изображения,  содержащихся в четырех байтах. Для работы с элемен-
         тами изображения в другой строке или столбце, происходит смещение
         с  A000 и центральный процессор считывает новый байт,  содержащий
         элементы изображения.
             Установка нескольких  управляющих регистров EGA влияет на то,
         что центральный  процессор  модифицирует,  заменяет или оставляет
         неизменными регистры-защелки. Доступ к этим регистрам осуществля-
         ется посредством одного из пяти индексированных чипов  сверхболь-
         ших  интегральных схем (СБИС) на плате EGA.  Установка чипов СБИС
         происходит направлением номера индекса,  соответствующего необхо-
         димой функции, одному из внутренних регистров EGA, и следующей за
         номером информацией для этой функции.  Существенно то, что индекс
         соответствует одному из многих внутренних регистров EGA,  но этот
         регистр связан с единственным портом вывода персонального  компь-
         ютера.  Данные для этих регистров направляются при помощи команды

                                      - 9-13 -
                    ЪДВДВДВДВДВДВДВДВДДДДДДДДДД¬   
                    ¦0¦0¦0¦0¦0¦0¦0¦0¦ Битовая  ¦   ¦
                    ГДБДБДБДБДБДБДБДЩ матрица 3¦   ¦   Регистры - защелки
                  ЪД†ДВДВДВДВДВДВДВДДДДДДДДДД¬ ¦   ¦    ЪДВДВДВДВДВДВДВД¬
                  ¦0¦0¦0¦0¦1¦1¦1¦1¦ Битовая  ¦ ¦        ¦0¦0¦0¦0¦0¦0¦0¦0¦ 3
                  ГДБДБДБДБДБДБДБДЩ матрица 2¦ ¦   Ц    АДБДБДБДБДБДБДБДЩ
                ЪД†ДВДВДВДВДВДВДВДДДДДДДДДД¬ ¦ ¦        ЪДВДВДВДВДВДВДВД¬
                ¦0¦0¦1¦1¦0¦0¦1¦1¦ Битовая  ¦ ¦ ¦   В    ¦0¦0¦0¦0¦1¦1¦1¦1¦ 2
                ГДБДБДБДБДБДБДБДЩ матрица 1¦ ¦ ¦        АДБДБДБДБДБДБДБДЩ
              ЪД†ДВДВДВДВДВДВДВДДДДДДДДДД¬ ¦ ¦ ¦   Е    ЪДВДВДВДВДВДВДВД¬
     A000:0000¦0¦1¦0¦1¦0¦1¦0¦1¦ Битовая  ¦ ¦ ГДЩ        ¦0¦0¦1¦1¦0¦0¦1¦1¦ 1
              ГДБДБДБДБДБДБДБДЩ матрица 0¦ ¦ ¦     Т    АДБДБДБДБДБДБДБДЩ
              ¦                          ¦ ГДЩ          ЪДВДВДВДВДВДВДВД¬
              ¦                          ¦ ¦       ¦    ¦0¦1¦0¦1¦0¦1¦0¦1¦ 0
              ¦                          ГДЩ       ¦    АДБДБДБДБДБДБДБДЩ
              ¦                          ¦         ¦     Позиции пикселей
              АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ         


                  Рис.9-1. Битовые матрицы и регистры-защелки EGA


         OUT процессора 80х86 или библиотечной функции языка Си outp().
             Например, EGA  имеет регистр битовой маски,  который защищает
         от изменения отдельные биты регистров-защелок.  Установка в  этом
         регистре бита в 0 маскирует соответствующий бит в регистре-защел-
         ке,  а установка в 1 разрешает изменять бит записью данных  цент-
         ральным процессором.  Кроме того, регистр битовой маски позволяет
         изменять отдельные элементы  изображения  без  изменения  смежных
         элементов изображения, адресуемых битом.
             Регистр битовой маски имеет номер функции 8 в графическом чи-
         пе  1  и 2 EGA.  Он программируется направлением индекса 8 в порт
         0х3CЕ и данных маски бита в порт 0хCF.  Следующие операторы языка
         Си установят  регистр маски бита для защиты всех битов, кроме би-
         та 2:

         outp(0x3CE, 8) ;      /* индекс маски бита */
         outp(0x3CF, 0x2) ;    /* все биты, кроме 2, в 0 */

         Но эти операторы не дают ключ к разгадке,   кроме комментирования
         того, что они делают. Чуть дальше  мы рассмотрим макрокоманду Си,
         с помощью которой проще установить регистры EGA.
             Вторым регистром EGA,  влияющим на перезапись содержания  ре-
         гистра-защелки, является регистр маски матрицы. Если любой из че-
         тырех битов этого регистра нулевой,  соответствующие битовые мат-
         рицы  (битовые массивы) защищены от изменения.  Направление числа
         между 0 и 15 в регистр маски матрицы запишет  в  битовые  матрицы
         EGA цвет,  соответствующий этому числу. Тем не менее, прежнее со-
         держание битовых массивов не стирается.  Оно должно  быть  стерто
         перед  установкой  нового значения цвета маски матрицы,  но после
         установки маски бита,  записью нуля в байт, содержащий изменяемый
         элемент  изображения.  Регистр маски матрицы является частью чипа
         планировщика EGA. Он доступен посредством направления индекса 2 в
         порт  0х3С4  и  направлением маски матрицы в порт 0х3С5.  Влияние
         маски бита и маски матрицы проиллюстрировано на рисунке 9-2.



                                      - 9-14 -




        ЪД¬  ЪДВДВДВДВДВДВДВД¬              ЪДВДВДВДВДВДВДВДВДДДДДДДДДД¬
     ЪДДґ1ГДДґ ¦ ¦1¦ ¦ ¦ ¦ ¦ ГДДДДДДДДДДДДДДґ ¦ ¦1¦ ¦ ¦ ¦ ¦ ¦ Битовая  ¦
     ¦  АДЩ  АДБДБДБДБДБДБДБДЩ              ГДБДБДБДБДБДБДБДЩ матрица 3¦
     ¦  ЪД¬  ЪДВДВДВДВДВДВДВД¬            ЪД†ДВДВДВДВДВДВДВДДДДДДДДДД¬ ¦
     ГДДґ1ГДДґ ¦ ¦1¦ ¦ ¦ ¦ ¦ ГДДДДДДДДДДДДґ ¦ ¦1¦ ¦ ¦ ¦ ¦ ¦ Битовая  ¦ ¦
     ¦  АДЩ  АДБДБДБДБДБДБДБДЩ            ГДБДБДБДБДБДБДБДЩ матрица 2¦ ¦
     ¦  ЪД¬  ЪДВДВДВДВДВДВДВД¬          ЪД†ДВДВДВДВДВДВДВДДДДДДДДДД¬ ¦ ¦
     ГДДґ0ГДДґ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ГДДДДДДДДДДґ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ Битовая  ¦ ¦ ¦
     ¦  АДЩ  АДБДБДБДБДБДБДБДЩ          ГДБДБДБДБДБДБДБДЩ матрица 1¦ ¦ ¦
     ¦  ЪД¬  ЪДВДВДВДВДВДВДВД¬        ЪД†ДВДВДВДВДВДВДВДДДДДДДДДД¬ ¦ ¦ ¦
     ГДДґ1ГДДґ ¦ ¦1¦ ¦ ¦ ¦ ¦ ГДДДДДДДДґ ¦ ¦1¦ ¦ ¦ ¦ ¦ ¦ Битовая  ¦ ¦ ГДЩ
     ¦  АДЩ  АДБДБДБДБДБДБДБДЩ        ГДБДБДБДБДБДБДБДЩ матрица 0¦ ¦ ¦
     АДДДДДДДДДДДДДД¬                 ¦                          ¦ ГДЩ
                 ЪДВБВДВДВДВДВДВД¬    ¦                          ¦ ¦
                 ¦0¦0¦1¦0¦0¦0¦0¦0¦    ¦                          ГДЩ
                 АДБДБДБВБДБДБДБДЩ    ¦                          ¦
                 ЪДВДВДВБВДВДВДВД¬    АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                 ¦1¦1¦1¦1¦1¦1¦1¦1¦
                 АДБДБДБДБДБДБДБДЩ

                  Рис.9-2. Регистры битовой маски и маски матрицы


                              Прямая запись на экран

             Зная о  регистрах  маски бита и маски матрицы,  а также о ре-
         гистрах-защелках EGA, мы имеем достаточно информации для создания
         программы на языке Си,  которая пишет точку непосредственно в эк-
         ранную память. Эта программа работает быстрее, чем аналогичная ей
         программа  BIOS  адаптера  EGA.  При  работе с 8-МГц персональным
         компьютером АТ, EGA BIOS поместит на дисплей в течении 1 миллисе-
         кунды  2.56  точки (2.56 точек/мс).  Программа,  представленная в
         листинге 9-3,  помещает на дисплей 7.55 точек/ мс - скорость воз-
         растает на 185 процентов.  Недостатком является то, что fastdot()
         может работать только в графических режимах EGA,  и  для работы с
         другой дисплейной картой  необходимо переписать программу.
             Следующие макрокоманды позволят программе 9-3 установить мас-
         ку  бита,  маску  матрицы,  а  также  другие внутренние регистры.
         #define EGA_GRFX(index, value) { outp(0X3CE, index) ; \
                                          outp(0x3CF, value) ;}
         #define EGA_SQNC(index, value) { outp(0X3C4, index) ; \
                                          outp(0x3C5, value) ;}

             Первая макрокоманда,  EGA_GRFX, принимает в качестве аргумен-
         тов номер индекса,  соответствующего функции,  выбранной из  чипа
         контроллера графики 1 и 2,  а также  значение, направляемое чипу.
         Графические чипы 1 и 2 EGA управляют доступом к битовым массивам.
         (Тем не менее,  они реально представляют собой два чипа, располо-
         женные по одному адресу,  и могут считаться одним  чипом.)  Адрес
         индекса графических чипов 1 и 2 0x3CE, а адрес данных 0x3CF. Мак-
         рокоманда охватывает два оператора языка Си.  Первый оператор по-
         сылает чипам значение индекса, а второй оператор посылает данные.
             Вторая  макрокоманда,  EGA_SQNC,  сходна  с EGA_GRFG. Однако,

                                      - 9-15 -
         EGA_SQNC имеет доступ к другому чипу, чипу планировщика EGA, нап-
         равляя индекс и данные различным портам вывода.  Наибольший инте-
         рес для чипа планировщика представляет регистр маски матрицы.
              Следующие две макрокоманды дают подпрограмме доступ к адресу
         сегмент:смещение  в любом месте адресного пространства персональ-
         ного компьютера:

         #define PEEK_BYTE(s,o) (*(char far *) ( (long)(s)<<16 | (o) ))
         #define PEEK_WORD(s,o) (*(int far *) ( (long)(s)<<16 | (o) ))

             Заключительные макрокоманды комбинируют предыдущие.  Макроко-
         манда GET_CRT_COLS() возвращает значение для использования в  ка-
         честве числа битов на линию в графических режимах EGA.  Число би-
         тов на линию равно числу символов на линию, и это число  является
         адресом   0x40:0x4A   в   области   данных   BIOS.   Макрокоманды
         EGA_BIT_MASK и EGA_MAP_MASK устанавливают соответственно регистры
         маски бита и маски матрицы.

         #define GET_CRT_COLS()   PEEK_WORD(0x40, 0x4A)
         #define EGA_BIT_MASK(mask)   EGA_GRFX(8, mask)
         #define EGA_MAP_MASK(mask)   EGA_SQNC(2, mask)
             Вместе эти макрокоманды облегчают чтение  и  понимание  кодов
         программы,  написанной для манипулирования аппаратными средствами
         EGA.  Эти макрокоманды далее используются во всех программах этой
         главы.
             Большинство регистров EGA доступны только для  записи. Каждая
         программа, использующая дисплей, нуждается в информации о состоя-
         нии EGA, так как только записываемые регистры нельзя считать. По-
         этому, самым безопасным состоянием регистров EGA является состоя-
         ние EGA BIOS,  установленное по умолчанию.  Кроме того,  EGA BIOS
         предполагает, что при записи символов на дисплей регистры EGA на-
         ходятся в состоянии,  установленном по  умолчанию.  Если  регистр
         маски бита установлен в состояние маскировки бита,  символы будут
         нечитаемыми.  Для маски бита и маски матрицы состоянием, установ-
         ленным по умолчанию,  является выключенный режим маскировки, поэ-
         тому,  установка  маски  0xF  и  0xFF  в  последних  двух строках
         fastdot() восстанавливает состояние по  умолчанию.  Преимуществом
         регистров  VGA  является то,  что они доступны и для чтения и для
         записи.  (Различия между EGA и VGA были использованы в  программе
         EGACHECK.C  для  обнаружения  карты  VGA.) Поэтому,  регистры VGA
         должны быть оставлены в состоянии по умолчанию.
             Убедитесь, что Вы понимаете,  как  вычисляется  в  программе,
         приведенной в листинге 9-3, адрес байта элемента изображения:

         char far *rgen = (char far *)(0xA0000000L +
                          (col >> 3) +
                          (row * GET_CRT_COLS()) );

         Адрес бита  вычисляется как ((строка х байты на строку) + столбцы
         / 8 бит на байт). В целях повышения скорости деления колонок на 8
         используется оператор правого сдвига языка Си, >>.

                         Листинг 9-3. Программа FASTDOT.C
         ---------------------------------------------------------------

         /* fastdot.c */
         #include <conio.h>

                                      - 9-16 -

         fastdot(row, col, color)
         /* Эта программа поместит точку в буфер дисплейной памяти EGA
         ** Использовать только в графических режимах EGA (13, 14, 15
         ** или 16) и с памятью EGA от 128К
         ** ИЛИ с VGA в режимах 13, 14, 15, 16, 17 или 18
         */
         int  row, col, color;
         {
         char latch ;
         /* Установить адрес изменяемого байта */
         /* Байт буфера = А000:((строка*байтов/строка)+столбец/8) */
           unsigned char far *rgen = (char far *)(0xA0000000L +
                                     (col >> 3)  +
                                     (row * GET_CRT_COLS()) ) ;
         /* Вычислить изменяемый бит: */
           char bit_mask = (char)(0x80 >> (col & 7)) ;
           EGA_BIT_MASK(bit_mask) ;   /* установить маску бита */
           latch = *(rgen) ;          /* подготовить защелки */
           *(rgen) = 0 ;              /* очистить бит */
           EGA_MAP_MASK(color) ;      /* установить цвет */
           *(rgen) = 0xFF ;           /* установить бит */
           EGA_MAP_MASK(0xF) ;        /* сбросить маску матрицы */
           EGA_BIT_MASK(0xFF) ;       /* сбросить маску бита */
         }
         ---------------------------------------------------------------

         Для того, чтобы узнать число бит на строку, значение которого мо-
         жет быть равно 40 байтам в видеорежиме 13 или 80 байтам в режимах
         с 14 по 16, посмотрите на значение числа символов на строку в об-
         ласти данных BIOS (адрес 0х40:0х4A).  Число бит на строку и число
         символов  на  строку в графических режимах EGA одно и то же.  Ре-
         зультат всего вычисления добавляется к 0xA0000000L,  представляю-
         щим собой сегмент графических режимов EGA.  Полное значение затем
         сбрасывается в указатель far.
             Номер бита  в  байте,  соответствующем  изменяемому  элементу
         изображения, вычисляется как (col~&~7). Как только будет известен
         номер  бита,  маска бита устанавливается в значение 0х80 >> номер
         бита (0х80 - это 010000000b).
             Предыдущая  программа предполагает, что используется страница
         0. Для того, чтобы получить возможность адресации к странице, от-
         личной от 0, вставьте следующие строки:

         while(page){
          rgen += PEEK_WORD(0x40, 0x4C) ; /* добавить длину страницы */
          --page ;}

         где page  - номер адресуемой страницы.  Слово по адресу 0х40:0х4С
         содержит  длину в байтах дисплейного буфера,  используемого  под-
         программами EGA BIOS.
             Испытайте программу, приведенную в листинге 9-2, заменив опе-
         ратор  dot  оператором fastdot(). Она в два-три раза быстрее, чем
         подпрограмма BIOS.




                                      - 9-17 -
                                    Много точек

             Для достижения максимальной производительности уникальных ап-
         паратных средств EGA необходимо написать много функций. Например,
         подпрограмма fastdot() устанавливает маску бита и маску матрицы в
         требуемые значения в начале,  а затем в конце сбрасывает эти  ре-
         гистры  в  первоначальное  состояние BIOS.  Если функция вызывает
         подпрограмму fastdot неоднократно, сброс регистра в конце fastdot
         повторять необязательно. Это замедляет работу функции.
             Программа, представленная в листинге 9-4,  включает  подпрог-
         рамму  вычерчивания  линий,  основанную  на алгоритме Бресенгама.
         Этот алгоритм первоначально использовался для управления цифровы-
         ми плоттерами,  но он также приемлем для дисплейной графики с би-
         товыми матрицами. Алгоритм всегда увеличивается (или уменьшается)
         на  1  в направлении или X или Y.  Выбор направления X или Y осу-
         ществляется увеличением наклона линии.  Если подъем  (направление
         Y)  больше,  то  увеличивается  (или уменьшается) Y;  если прогон
         (направление X) больше,  то увеличивается  (или  уменьшается)  X.
         Увеличение  или уменьшение X или Y выбирается направлением линии.
         Термин "совокупная ошибка" используется при увеличении или умень-
         шении в перпендикулярном направлении.
             Вместо вызова подпрограммы fastdot(), точки помещаются непос-
         редственно  на  экран  дисплея.  Регистры EGA сбрасываются только
         один раз в конце и функция работает гораздо быстрее функции,  ос-
         нованной на вызове fastdot().

                           Листинг 9-4. Программа BRES.C
         ---------------------------------------------------------------

         /* bres.c */
         /* Чертит серию линий для демонстрации функции line() */
         #include <conio.h>
         #include <dos.h>
         #include <stdio.h>
         #include "ega.h"
         void line(int, int, int, int, int); /* добавить это в ega.h */

         main()
         {
           int x1, y1, x2, y2 ;
           int step = 10, color = 13, scan_lines ;
           struct Ega_info info ;

           if(get_ega_info(&info) >= 128) /* активный EGA? память? */
           {
             set_crt_mode(info.high_res_grahics) ;
             scan_lines = (PEEK_BYTE(0x40, 0x84) + 1)
                           * PEEK_WORD(0x40, 0x85) ;
             y2 = (scan_lines - 1) - ((scan_lines - 1) % step) ;
             for (y1 = 0, x1 = 0, x2 = 0;
                  y1 <= y2;
                  y1 += step, x2 += step)
                line(x1,y1,x2,y2,color) ;
             getch() ;  /* ждать нажатия клавиши */
           set_crt_mode(info.text_mode);
           }
           else

                                      - 9-18 -
           puts("\nАдаптер EGA не активен или не установлен.\n") ;
         }

         void line(x1,y1,x2,y2,color)
         int x1,y1,x2,y2,color ;
         /* Быстрая функция линии - использует алгоритм Бресенгама */
         /* Координаты строк(Y) и столбцов(X) считаются не равными */
         #define sign(x) (((x) < 0) ? (-1) : (1))
         #define qabs(x) (((x) < 0) ? -(x) : (x))
         {
         int dx = qabs(x2 - x1) ; /* прогон */
         int dy = qabs(y2 - y1) ; /* подъем */
         int s1 = sign(x2 - x1) ; /* для увеличения/уменьшения */
         int s2 = sign(y2 - y1) ;
         int dx2, dy2, bytes_per_line = GET_CRT_COLS() ;
         registr error_term, i ;
         unsigned char far *rgen = (char far *)(0xA0000000L) ;
         unsigned char exchange = (char)0 ;

         /* Большее значение подъема или прогона определяет,
         ** что увеличивать в цикле
         */
         if(dy > dx)
           { int temp = dx; dx = dy; dy = temp; exchange = (char)1; }

           dx2=(dx << 1); /* использовать повторно, вычислить сейчас */
           dy2=(dy << 1);
           error_term=(dy - dx) << 1; /* инициализировать error_term */
           EGA_GRFX(0, color) ;  /* использовать регистр  EGA
                                    установить/сбросить */
           EGA_GRFX(1, 0xF) ;    /* разрешить все битовые массивы */
           for (i=1; i<=dx; ++i) /* все элементы изображения на линии */
           {
           EGA_BIT_MASK(0x80 >> (x1 & 7) ) ;
           rgen[ ((x1 >> 3) + (y1 * bytes_per_line)) ] += 0x1 ;
              while (error_term >= 0)  /* цикл до следующего элемента */
              {
               if (exchange)
                   x1 += s1 ;
               else
                   y1 += s2 ;
              error_term -= dx2 ;
              }
              if (exchange)
                   y1 += s2 ;
              else
                   x1 += s1 ;
              error_term += dy2 ;
           }
         EGA_GRFX(1, 0) ;   /* запретить регистр установить/сбросить */
         EGA_BIT_MASK(0xFF) ; /* сбросить маску бита */
         }
         ---------------------------------------------------------------

             Для хранения графического образа на экране  программа  должна
         знать  высоту  и  ширину дисплея в элементах изображения.  Ширина
         дисплея в элементах изображения указывается как  GET_CRT_COLS() x

                                      - 9-19 -
         х 8 элементов/байт. Высота должна быть точно определена из табли-
         цы, содержащей количество вертикальных линий развертки для каждо-
         го режима.  Однако,  это более быстрый, но менее точный способ. И
         число строк символов, и размер точки (байта на символ) можно зап-
         рограммировать, кроме того, любой из них можно изменить. Но высо-
         та прямоугольника для символа в байтах и  число  строк  развертки
         определяют  количество  строк.  Так как слово с адресом 0х40:0х85
         содержит байты на символ и байт с адресом 0:40х0:84 содержит  ко-
         личество строк,  они могут быть использованы для вычисления числа
         строк развертки для любого видеорежима. Оператор языка Си

         scan_lines = (PEEK_BYTE(0x40, 0x84) + 1)
                      * PEEK_WORD(0x40, 0x85) ;

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


                      Использование регистра установки/сброса

             В программе,  представленной в листинге 9-4,  функция  line()
         использует другой метод определения цвета точек на дисплее, неже-
         ли подпрограмма fastdot().  Подпрограмма fastdot() применяет  для
         определения  цвета  регистр маски бита.   Но  так как определение
         маски для регистра маски матрицы не очищает предыдущую  точку, то
         эта  точка  должна быть сначала сброшена при помощи установки ре-
         гистра маски бита в 0xF с последующей установкой цвета  для новой
         точки.  Другими словами,  для установки определенного цвета точки
         доступ и к маске матрицы,  и к памяти EGA должен быть осуществлен
         дважды.
             Функция line() использует регистр установки/сброса и для  оп-
         ределения цвета разрешает выполнить установку/сброс регистра. Ре-
         гистр установки/сброса установит байт в  0xFF  в  каждом  битовом
         массиве EGA,  бит которого в этом регистре равен единице, и сбро-
         сит байт в 0 в каждом битовом массиве,  бит которого равен  нулю.
         Следовательно, предыдущее содержание регистров-защелок заменяется
         номером цвета,  соответствующего значению,  установленному в  ре-
         гистре  установки/сброса.  Регистр маски матрицы не влияет на ре-
         гистр установки/сброса,  но регистр маски бита полезен для защиты
         смежных элементов изображения.
             Чтобы использовать регистр установки/сброса, Вы сначала долж-
         ны  разрешить его с помощью регистра разрешения установки/сброса.
         Регистр установки/сброса и  регистр  разрешения  установки/сброса
         являются  частью графического контроллера EGA.  Состояние BIOS по
         умолчанию для регистра разрешения установки/сброса  0,  это озна-
         чает,  что регистр установки/сброса выключен. Каждый бит из четы-
         рехбитового  значения, направляемого  регистру  разрешения  уста-
         новки/сброса, соответствует битовой матрице EGA. Если бит  в  ре-
         гистре разрешения  установки/сброса  0,  соответствующая  битовая
         матрица защищена от изменения регистром установки/сброса.
             Регистр установки/сброса доступен направлениям  индекса  0  в
         порт  0х3CЕ и последующим направлением четырехбитового кода цвета
         в порт 0x3CF.  Регистр установки/сброса оказывает влияние  только
         на  битовые  матрицы,  разрешенные  регистром разрешения установ-

                                      - 9-20 -
         ки/сброса. Регистр разрешения установки/сброса доступен направле-
         нием индекса 1 в порт 0х3СЕ с последующим направлением четырехби-
         товой маски матрицы в порт 0x3CF.
             Обратите внимание на оператор  rgen[((x1 >> 3) + (y1 * bytes_
         per_line))]+@ 0x1 ;  в программе 9-4.  Поскольку дисплейный буфер
         EGA  линейный,  к нему легко обратиться как к матрице.  Выражение
         внутри скобок вычисляет смещение буфера изменяемого байта.  Похо-
         же,  что правая сторона оператора прибавляет 1 к байту, что цент-
         ральный процессор и пытается проделать.  Тем не  менее,  действи-
         тельной    целью   является   предохранение   смежных   элементов
         изображения,  содержащихся в байте.  Когда  используется  регистр
         маски  бита,  необходимо сначала считать дисплейный буфер для за-
         полнения регистров-защелок таким образом,  чтобы  другие  биты  в
         этом  байте были защищены.  В отличие от метода установки цвета с
         помощью регистра маски бита, при использовании регистра установки
         /сброса байт,  посылаемый центральным процессором,  служит только
         для установки адреса изменяемого байта.
             Таким образом,  выражение +=1 выполняет две функции: оно счи-
         тывает дисплейный буфер в целях  установки  регистров-защелок,  а
         также возвращает байт, который включает регистр установки/сброса.
         В процессе трансляции компилятором языка Си  операции  в  команду
         процессора 80х86 1 может принимать любое значение, которое снача-
         ла считывает, а затем сохраняет байт в дисплейной памяти EGA.


                         Использование режимов записи EGA

             EGA имеет три режима записи: 0, 1 и 2. Изменение режима запи-
         си меняет реакцию аппаратных средств EGA на направление централь-
         ным процессором байта в дисплейный буфер. Каждый режим записи оп-
         тимизирован для различного использования. Режим записи 0 является
         режимом  записи  общего назначения,  режим записи 1 оптимизирован
         для копирования областей памяти EGA и режим  записи  2  наилучшим
         образом  используется  для закрашивания.  Изменение режима записи
         может существенно ускорить выполнение операции.
             Режим записи  0 является режимом,  применяемым EGA BIOS.  Это
         наиболее общий режим записи. В режиме 0 цвет элемента изображения
         может быть установлен с помощью как регистра маски матрицы, так и
         регистра установки/сброса. Регистр маски матрицы используется EGA
         BIOS и подпрограммой fastdot().  Функция line() применяет регистр
         установки/сброса для определения цвета. При использовании регист-
         ра  маски матрицы отдельные элементы изображения могут быть уста-
         новлены посылаемым центральным процессором байтом с соответствую-
         щими битами,  установленными в 1.  Тем не менее, смежные элементы
         изображения в байте должны быть защищены с помощью регистра маски
         бита.  При  применении регистра установки/сброса биты посылаемого
         центральным процессором байта не соответствуют элементам  изобра-
         жения. Байт записывается только для определения смещения изменяе-
         мых  элементов  изображения.  Цвет  определяется в регистре уста-
         новки/сброса, а регистр маски бита позволяет управлять отдельными
         элементами изображения.
             Режим записи  2  очень  похож  на режим записи 0.  В режиме 2
         байт,  посылаемый центральным процессором,  скорее  устанавливает
         цвет, чем отдельные элементы изображения. Регистр маски бита поз-
         воляет управлять отдельными элементами изображения  и,  если  ре-
         гистр  маски бита не установлен,  весь байт элементов изображения
         заполняется цветом, значение которого посылается центральным про-

                                      - 9-21 -
         цессором.  Режим записи определяется в битах 0 и 1 байта, направ-
         ляемого регистром режима графических чипов 1 и 2.  Регистр режима
         имеет индекс 5. Программа, приведенная в листинге 9-5, демонстри-
         рует режим записи 2.  Подпрограмма rect() использует режим 2  для
         заполнения прямоугольника заданным цветом.

                           Листинг 9-5. Программа RECT.C
         ---------------------------------------------------------------

         /* rect.c */
         /* Эта программа демонстрирует режим записи 2 */
         #include <conio.h>
         #include <dos.h>
         #include <stdio.h>
         #include "ega.h"

         void rect(int,int,int,int,char); /* добавить это в ega.h */

         main()
         {
         int i,j;
         struct Ega_info info ;
         if(get_ega_info(&info))
            set_crt_mode(info.high_res_graphics) ;
         else
            return(1) ;
         printf("\nЦвет #:\n");
         for (i=0,j=0;i<16;++i,j+=40)
         {
            printf(" %2i  ",i);
            rect(50,j,349,j+39,(char)i);
         }
         getch();
         set_crt_mode(3) ;
         }
         void rect(row1,col1,row2,col2,color)
         int col1,row1,col2,row2 ;
         char color ;
         { /* Эта функция генерирует заполненный прямоугольник */
           /* Предполагается, что row1 < row2 и col1 < col2 */
         unsigned char far *rgen = (char far *)(0xA0000000L) ;
         int rows = row2 - row1 ;  /* число строк */
         int cols = (col2 >> 3) - (col1 >> 3) - 1; /* всего столбцов */
         char left = (char)(0xFF >> (col1 & 7)) ;  /* левая маска */
         char rght = (char)~(0xFF >> (col2 & 7)) ; /* правая маска */
         char next_row ;
         char byte_per_line = (char)GET_CRT_COLS() ;
         register x,y ;
         char latch ;

         if (cols < 0)   /* Находятся ли col1 и col2 в одном байте? */
             left &= rght, cols = 0, rght = 0 ;
         rgen += bytes_per_line*row1 +(col1 >> 3) ;
         next_row = bytes_per_line - cols - 2 ;

         EGA_GRFX(5,2);               /* установить режим записи 2 */
         for(y = 0 ; y < rows ; y++)  /* выполнить каждую строку */

                                      - 9-22 -
         {
           EGA_BIT_MASK(left) ; /* установить левую маску бита */
           latch = *(rgen) ;    /* защелкнуть битовые матрицы EGA */
           *(rgen++) = color ;  /* установить цвет, указать для
                                   следующего байта */
           EGA_BIT_MASK(0xFF) ; /* отменить маску центра */
           for(x = 0; x < cols; x++)  /* выполнить каждый столбец */
              {
                 latch = *(rgen) ;
                 *(rgen++) = color ;
              }
           EGA_BIT_MASK(rght) ; /* установить правую маску бита */
           latch = *(rgen) ;    /* защелкнуть битовые матрицы EGA */
           *(rgen++) = color ;  /* установить цвет */
           rgen += next_row ;   /* перейти к следующей строке */
         }
         EGA_BIT_MASK(0xFF) ;   /* отменить маску бита */
         EGA_GRFX(5,0) ;        /* сбросить режим записи */
         }
         ----------------------------------------------------------------

             В программе  9-5 режим записи 2 устанавливается макрокомандой
         EGA_GRFX(5,2).  Вы должны быть внимательными и не посылать значе-
         ния, отличные от 0, 1, или 2 для EGA (0, 1, 2 или 3 для VGA), так
         как другие биты байта, направляемые регистру режима, имеют значе-
         ние для EGA. Регистры маски матрицы и маски бита эффективны в ре-
         жиме записи 2, а регистр установки/сброса бесполезен. Режим запи-
         си 0,  режим записи BIOS по умолчанию,  устанавливается с помощью
         EGA_GRFX(5,0).  Перед использованием  других программ или вызовов
         BIOS режим записи должен быть сброшен в 0.
             Режим записи 1 применяется для быстрого копирования одной об-
         ласти памяти EGA в другую. Это очень полезно для прокрутки, муль-
         типликации, а также сохранения и восстановления областей на экра-
         не.  Режим  записи 1 позволяет Вам копировать 4 байта в каждой из
         четырех битовых матриц за один цикл чтения или записи центрально-
         го процессора и во много раз быстрее чтения 4 отдельных байтов из
         битовых матриц с последующей записью 4 байтов по новому адресу.
             Для копирования  8 элементов смещение изображения памяти EGA,
         содержащее 8 элементов изображения,  считывается  для  подготовки
         регистров-защелок; затем смещение, содержащее назначение, записы-
         вается центральным процессором. Когда центральный процессор запи-
         сывает байт,  а режим записи установлен в 1,  EGA сбрасывает байт
         центрального процессора и копирует содержание регистров-защелок в
         каждую битовую матрицу.  Регистр маски бита не используется с ре-
         жимом записи 1. Все четыре байта в регистрах-защелках записывают-
         ся  во  все  четыре битовые матрицы независимо от установки маски
         бита. Регистр маски матрицы может быть использован для защиты от-
         дельных битовых матриц.
             Программа, представленная в листинге 9-6, демонстрирует режим
         записи 1.  Серия линий чертится в верхней части экрана. Затем эта
         серия копируется с использованием режима записи 1.  Наконец, гра-
         ница  образца быстро перечерчивается для демонстрации возможности
         мультипликации.



                                      - 9-23 -
                          Листинг 9-6. Программа MODEL.C
         ---------------------------------------------------------------
         /* mode1.c */
         /* Эта программа демонстрирует режим записи 1 EGA */
         #include <conio.h>
         #include <dos.h>
         #include <stdio.h>
         #include "ega.h"
         void copy( int,int,int,int,int,int );
         void main()
         {
            registr i,j;
            int k = 0;
            set_crt_mode(16) ; /*только усовершенствованный монитор!*/
            /* Рисует интересующую серию: */
            for(k = 0; k <= 4; ++k)
            for(j = 0+k; j <= 500+k; j += 5)
            for(i = 0+k; i <= 100+k; ++i)
               fastdot(i,i+j,13) ;
            for(k = 0; k <= 3; ++k)
            for(j = 0+k; j <= 500+k; j += 5)
            for(i = 0+k; i <= 100+k; ++i)
               fastdot(i,i+j,3) ;
            /* Скопировать серию на 120 строк вниз: */
            copy(0,0,105,639,    120,0) ;
            while(!kbhit())
            {
         /* Скопировать границу повторно,
         ** создает иллюзию движения: */
                copy(99,100,106,592,   219,100) ;
                copy(99,100,106,592,   219,108) ;
            }
            set_crt_modes(3) ;
         }

         void copy(r1_1, c1_1, r2_1, c2_1, r1_2, c1_2)
         int  r1_1, c1_1,   /* верхний левый угол источника */
              r2_1, c2_1,   /* нижний правый угол источника */
              r1_2, c1_2 ;  /* верхний левый угол назначения */
         {
         /* Быстро копирует одну область экрана в другую.
         ** Использует режим записи 1. Надо задать только
         ** верхний угол назначения.
         */
            char far *source = (char far *)(0xA0000000L) ;
            char far *destination = (char far *)(0xA0000000L) ;
            int rows = r2_1 - r1_1 ;
            int cols = (c2_1 >> 3) - (c1_1 >> 3) ;
            int bytes_per_line = GET_CRT_COLS() ;
            int next_row = bytes_per_line - cols ;
            registr x,y ;
            source += bytes_per_line * r1_1 + (c1_1 >> 3) ;
            destination += bytes_per_line * r1_2 + (c1_2 >> 3) ;

            EGA_GRFX(5,1) ;     /* установить режим записи 1 */
            for(y = 0 ; y < rows ; y++)
            {

                                      - 9-24 -
               for(x = 0; x < cols; x++)
                 *(destination++) = *(source++) ;
               source += next_row ;
               destination += next_row ;
            }
            EGA_GRFX(5,0) ;   /* сбросить режим записи */
         }
         ----------------------------------------------------------------

             Поскольку регистр  маски бита не пригоден для использования в
         режиме записи 1, подпрограмма copy() будет копировать восемь эле-
         ментов  изображения  исходных байтов в байты назначения.  Другими
         словами,  режим записи 1 применяется для манипулирования байтами,
         а  не  элементами изображения.  Режим записи 1 можно использовать
         для сохранения области экрана на невидимой странице.  Это полезно
         для  реализации спускающихся меню.  Область под спускающимся меню
         может быть сохранена на невидимой странице и  затем восстановлена
         после использования меню. Режим записи 1 способен копировать дан-
         ные только в другую область памяти EGA. Для чтения цвета из памя-
         ти EGA требуется отдельное чтение четырех битовых матриц.
             VGA имеет один новый режим записи.  Режим записи 3 VGA сходен
         с использованием пары регистров установки/сброса и разрешения ус-
         тановки/сброса в режиме записи 0 EGA для установки цвета  (приме-
         няется подпрограммой line() программы 9-4). Разница заключается в
         том, что в режиме записи 3 регистр разрешения установки/сброса не
         используется,  поэтому значение, содержащееся в регистре установ-
         ки/сброса не маскируется регистром разрешения установки/сброса.


                               Чтение битовых матриц

             Поскольку каждый  байт  адресного  пространства  центрального
         процессора, зарезервированного для EGA, представляет 4 байта гра-
         фической  памяти,  память  EGA не может быть прочитана непосредс-
         твенно центральным процессором.  EGA возвратит  байт  из  битовой
         матрицы, выбранной в регистре выбора чтения битовой матрицы. Счи-
         тываемую матрицу необходимо  установить  перед  чтением  смещения
         EGA, содержащего интересующие Вас элементы изображения.
             Для определения цвета заданного элемента изображения требует-
         ся отдельное чтение из каждой битовой матрицы. Каждый бит 4-бито-
         вого значения цвета расположен в одной из четырех битовых матриц.
         Самый  значимый бит значения цвета находится в битовой матрице 3,
         а менее значимый бит в битовой матрице 0.  Регистр выбора  чтения
         матрицы  имеет  индекс  4 в графических чипах 1 и 2 EGA.  Так как
         каждая битовая матрица EGA должна считываться  отдельно, значение
         регистра выбора чтения матрицы соответствует только одной битовой
         матрице EGA в один момент времени.
             Функция в  программе 9-7 возвращает цвет элемента изображения
         на дисплее.  Так же как и fastdot(),  она читает цвет точки в не-
         сколько раз быстрее, чем эквивалентная подпрограмма BIOS.
             Смещение байта, содержащего элемент изображения, определяется
         точно также,  как и в подпрограмме fastdot(). Значение маски бита
         вычисляется определением номера бита изменяемого байта. Но значе-
         ние  маски  бита не посылается в регистр маски бита EGA.  Регистр
         маски бита EGA не влияет на байты,  читаемые EGA.  Маска бита ис-
         пользуется для изоляции элемента изображения от байта,  считывае-
         мого из битовой матрицы EGA.  Биты затем прибавляются матрица  за

                                      - 9-25 -
         матрицей к коду цвета элемента изображения. Регистр выбора чтения
         матрицы выбирает матрицу для чтения.  Битовые матрицы читаются  в
         обратном  порядке (матрица 3,  2,  1,  0),  так как это облегчает
         трансляцию кода.  Обратите внимание,  что регистр  выбора  чтения
         матрицы не сбрасывается в конце подпрограммы. При выполнении цик-
         ла в последний раз регистр выбора чтения  матрицы устанавливается
         в 0, значение по умолчанию.

          Листинг 9-7. Программа, возвращающая цвет элемента изображения
         -----------------------------------------------------------------

         /* возвращает цвет элемента изображения */
         int readdot(row,col)
         int row,col;
         {
            registr color = 0 ;
            registr latch ;
            unsigned char far *rgen = (char far *)(0xA0000000L +
                                      (col >> 3) +
                                      (row * GET_CRT_COLS())) ;
            int bit_number = (col & 7)^7 ;
            int bit_mask = (1 << bit_number) ;
            int plane ;
            /* Пройти через каждый массив 3, 2, 1, 0 */
            for(plane = 3; plane >= 0; plane--)
            {
               EGA_GRFX(4,plane) ;           /* выбрать массив */
               latch = *(rgen) & bit_mask ;  /* бит из этого массива */
               latch >>= bit_number ;        /* выровнять справа */
               color <<= 1 ;            /* место для следующего бита */
               color |= latch ;         /* прибавить бит */
            }
            return(color) ;
         }
         ----------------------------------------------------------------

                               Цветовые палитры EGA

             При использовании  с усовершенствованным цветным дисплеем EGA
         может отображать любые  16  цветов  из  64-цветной  палитры.  Для
         представления  16  цветов требуется 4 бита.  Каждый из этих битов
         соответствует одной из четырех битовых матриц EGA.  В режиме  CGA
         или в исходной палитре EGA 4 бита соответствуют красному, зелено-
         му, синему  ярким   цветам,  что обычно описывается IRGB.  Но как
         только исходная палитра EGA изменится, то 4-битовый код цвета бу-
         дет представлять собой просто индекс новой палитры.
             64-цветная палитра имеет те же три  основных  цвета  (красный,
         зеленый,   синий),  что  и  16-цветная  палитра,  но  бит  яркости
         отсутствует.  Вместо него каждый цвет имеет 2 бита для яркости от-
         дельного цвета,  что дает три уровня яркости каждого цвета.  Таким
         образом 64-цветная палитра может быть  представлена  6  битами  (3
         цвета  х  2 бита/цвет).  Биты меньшей яркости трех цветов являются
         самыми значимыми битами в 6-битовом значении  и  аббревиатура  rgb
         соответствует  красному,  зеленому  и синему цветам малой яркости.
         Самые незначимые 3 бита представляют красный, зеленый и синий цве-
         та  высокой  яркости  и  им соответствует аббревиатура RGB.  Общее
         6-битовое значение,  rgbRGB   используется для выбора одного из 64

                                      - 9-26 -
         цветов. Как только один из 16  отображаемых  цветов  установлен  в
         значение rgbRGB,  этот цвет может быть выбран с помощью 4-битового
         значения  IRGB.  Биты  значения  rgbRGB  будут  всегда  показывать
         красный,  зеленый и синий компоненты результирующего цвета,  но  с
         подключенным усовершенствованным цветным дисплеем,  значение  IRGB
         является просто индексом текущей палитры.
             Цвета rgbRGB могут применяться только с  EGA,  подключенным  к
         расширенному  цветному  монитору.  Когда  EGA подключен к цветному
         дисплею, могут использоваться только 16 цветов исходной палитры. В
         текстовых режимах и графических режимах EGA отдельные регистры па-
         литры могут быть установлены в любой из 16 исходных цветов.  В ре-
         жимах,  совместимых с CGA, палитра должна изменяться с использова-
         нием CGA-совместимых вызовов BIOS.
             EGA также  имеет  регистр  выходящей  строки развертки.  Цвет,
         посылаемый в этот регистр, отображается как граница. Тем не менее,
         выходящая  строка развертки приемлема только в режимах с 200 стро-
         ками развертки.
             Регистры палитры  EGA чаще всего устанавливаются с помощью но-
         вых вызовов EGA BIOS.  Вызов BIOS может установить или один из  16
         цветов, или все 16 цветов сразу. Вызов BIOS является функцией 0х10
         прерывания 0х10.  Имеется четыре подфункции:  0 устанавливает  от-
         дельные регистры палитры в любое значение rgbRGB (или в любое зна-
         чение IRGB,  если EGA не подключен к усовершенствованному цветному
         дисплею),  1  устанавливает регистр выходящей строки развертки,  2
         устанавливает все регистры палитры и регистр выходящей строки раз-
         вертки,  и  4  служит  переключателем между мерцанием текста и яр-
         костью. Подфункция выбирается в регистре AL.

                                Установить палитру

             Результат:  Int   0x10
             Вызывается: AH =  0х10
                         AL =  0, Установить отдельный регистр палитры
                         BL =  Изменяемый номер цвета (IRGB)
                         BH =  Установить значение rgbRGB
                         AL =  1, Установить регистр выходящей строки
                               развертки
                         BH =  Номер устанавливаемого цвета
                         AL =  2, Установить все регистры палитры и ре-
                               гистр выходящей строки развертки
                               ES:DX указывает на 17-байтовую таблицу
                               Байты 0 - 15 имеют 16 значений rgbRGB для
                               цветов 0 - 15
                               Байт 16 является номером цвета для ре-
                               гистра выходящей строки развертки
                         AL =  3, Переключает бит яркость/мерцание
                               Меняет значение бита 7 байта атрибута
                               текста
                         BL =  0, Разрешает яркость фона
                         BL =  1, Разрешает мерцание переднего плана
             К сожалению,  регистры палитры  EGA  только  записываемые.  В
         обычном  состоянии  невозможно определить,  какое значение rgbRGB
         представляет заданный номер цвета. При изменении регистров палит-
         ры EGA BIOS проверит наличие 256-байтовой таблицы, называемой об-
         ластью сохранения параметров.  Если эта таблица имеется,  то BIOS
         сохранит в ней значения rgbRGB. Здесь не рассматривается создание
         и сопровождение области сохранения параметров, но важно использо-

                                      - 9-27 -
         вать  вызовы BIOS для установки палитры таким образом,  чтобы эта
         таблица параметров обновлялась.
             VGA имеет  считываемые/записываемые регистры палитры, поэтому
         отдельные цвета rgbRGB могут быть определены чтением соответству-
         ющего регистра палитры.  VGA также имеет более широкую палитру, в
         которой любой из 16 отображаемых цветов может быть цветом из  па-
         литры 262.144  возможных цветов. Вместо 2 битов на цвет в EGA, VGA
         использует 6 битов на цвет (2 х 3 цвета = 262.144 цвета).
             Программа, представленная  в листинге 9-8,  демонстрирует ис-
         пользование регистров палитры.  Эта программа работает  только  в
         сочетании  EGA  с усовершенствованным цветным дисплеем (или VGA).
         Сначала программа,  применяя функцию rect() программы 9-5, начер-
         тит  16  цветных прямоугольников.  Затем палитра будет непрерывно
         меняться.

                         Листинг 9-8. Программа PALETTE.C
         ---------------------------------------------------------------

         /* palette.c */
         /* Демонстрирует палитру 64 цветов */
         #include <conio.h>
         #include <dos.h>
         #include <stdio.h>
         #include "ega.h"

         void set_all_pal(char*) ;
         void gotoXY(int,int) ;
         main()
         {
         int i,j,ch = 0;
         char palette[17] ;  /* эта матрица содержит палитру */
         set_crt_mode(16) ;  /* имейте правильный монитор! */

         /* Начертить несколько цветных штрихов: */
         printf("\nЦвет #:\n") ;
         for (i=0,j=0;i<16;++i,j+=40)
         {
            printf (" %2i ",i);
            rect(50,j,300,j+39,i);    /* из программы 9-5 */
            palette[i] = (char)i ;    /* инициализировать матрицу */
         }
            gotoXY(15,22) ;
            printf("rgbRGB цвета 7") ;
            printf("%c%c%c%c%c%c",205,205,205,205,205,190) ;
            gotoXY(20,0) ;
            printf("Нажмите Space для одного пробела, Esc для выхода");

            palette[16] = (char)0 ;
            while(ch != 27)          /* пока не нажата клавиша Esc */
               {
               if (kbhit())          /* если клавиша нажата, */
                 ch = getch() ;      /* принять символ */
               for (i = 1; i<=15; ++i)
                 {
                    palette[i]++;
                    if (palette[i] == 64)
                    /* максимальное значение rgbRGB */

                                      - 9-28 -
                        palette[i] = 1 ;
                 }
                 set_all_pal(palette) ;   /* установить палитру */
                 gotoXY(15,23) ;
                 /* Перевести значение rgbRGB в двоичное */
                 for(i = 5;i>=0; --i)
                 if(palette[7] & 1<<i)
                    putchar('1') ;
                 else
                    putchar('0') ;
                 if(ch == 32)         /* режим одного пробела */
                    while(!kbhit());
               }

         set_crt_mode(3) ;
         }

         /*==========================================================*/
         void set_all_pal(palette)
         char *palette ;
         /* Эта функция устанавливает всю палитру */
         {
         union REGS regs ;       /* регистры 8086 */
         struct SREGS segregs ;
         char far *fp = (char far *)palette ;
         regs.h.ah = 0x10 ;
         regs.h.al = 2 ;           /* функция для установки всего */
         segregs.es = FP_SEG(fp);  /* ES в сегмент палитры */
         regs.x.dx = FP_OFF(fp) ;  /* DX в смещение палитры */
         int86x(0x10, &regs, &regs, &segregs) ;
         }

         /*==========================================================*/
         void gotoXY(x,y)
         int x,y ;
         /* Эта функция перемещает курсор текста в x,y */
         {
            union REGS regs ;
            regs.h.ah = 2 ;            /* установить функцию курсора */
            regs.h.bh = 0 ;               /* страница 0 */
            regs.h.dh = (char)y ;         /* строка */
            regs.h.dl = (char)x ;         /* столбец */
            int86(0x10, &regs, &regs) ;   /* вызов прерывания 0х10 */
         }
         ----------------------------------------------------------------

                        Регистр циклического сдвига данных

             Регистр циклического сдвига данных позволяет Вам выбрать спо-
         соб комбинирования данных,  направляемых центральным процессором,
         в регистрах-защелках EGA.  Дополнительные параметры, имеющие дан-
         ные,  будут  логически  умножены,  сложены, сложены по модулю или
         оставлены неизменными с байтом в регистрах-защелках. Несмотря  на
         то,  что  регистр циклического сдвига данных может сдвигать байт,
         направляемый центральным процессором, на практике этому придается
         мало  значения.  Центральный процессор быстрее осуществляет сдвиг
         байта и требует менее продолжительную  установку.  Значащие  биты

                                      - 9-29 -
         регистра  циклического  сдвига данных показаны на рисунке 9-3,  а
         пример использования этого регистра приведен в программе 9-9.

            ЪДВДВДВДВДВДВДВД¬
            ¦7¦6¦5¦4¦3¦2¦1¦0¦
            АДБДБДБДБДБДБДБДЩ
             АДВДЩ АВЩ АДВДЩ
               ¦    ¦    АДДДДДДДДД Счетчик циклического сдвига байта CPU
               ¦    ¦
               ¦    АДДДДДДДДДДДДДД Выбор функции
               ¦                       БИТЫ
               ¦                       4  3
               ¦                       0  0  Данные не изменяются
               ¦                       0  1  CPU байт ANDбайты защелки
               ¦                       1  0  CPU байт ORбайты защелки
               ¦                       1  1  CPU байт XORбайты защелки
               АДДДДДДДДДДДДДДДДДДД Не используется на EGA

                   Рис.9-3. Регистр циклического сдвига данных



              Листинг 9-9. Пример использования регистра циклического
                                   сдвига данных
         ---------------------------------------------------------------

         main()
         {
            int i,j,k ;
            for(k=1;k<16;k++)
               {
               set_crt_mode(16) ;
               rect(0,0,200,639,k) ;    /* фон */
               EGA_GRFX(3,0) ;          /* сбросить регистр */
               for(i=0;i<13;i++)
               printf("\n") ;
               printf("   Не изменено    логически умножено") ;
               printf("   \t логически сложено    сложено по модулю") ;
               for(i=0, j=0, i<4; j=160*(i+1),i++)
               {
                 switch(i)
                  {
                    case 1:EGA_GRFX(3,8) ;
                    break;  /* логически умножить содержание регистра */
                    case 2:EGA_GRFX(3,16) ;
                    break;   /* логически сложить содержание регистра */
                     case 3:EGA_GRFX(3,24) ;
                     /* сложить по модулю содержание регистра */
                   }
                     /* Сейчас начертить прямоугольники: */
                     rect(20,20+j,100,99+j,1) ;
                     rect(40,40+j,120,119+j,1<<1) ;
                     rect(60,60+j,140,139+j,1<<2) ;
                     rect(80,80+j,160,159+j,1<<3) ;
                   }
               gerch() ;
               }

                                      - 9-30 -
         set_crt_mode(3) ;
         }
         ---------------------------------------------------------------

                         Режим отображения 256 цветов VGA

             VGA имеет новый видеорежим,  режим 19,  который может отобра-
         жать 256 цветов из палитры 262.144 цветов. Концептуально режим 19
         достаточно прост.  Так как каждый элемент изображения представля-
         ется одним байтом дисплейной памяти, упрощены вычисления, опреде-
         ляющие смещение каждого элемента изображения.   Программа,  пред-
         ставленная в  листинге  9-10,  служит  примером  режима  19  VGA.
         Отображаются все 256 цветов. Первые 16 цветов такие же, как цвета
         палитр CGA,  VGA и EGA.  Следующие  21  цвет  принадлежат  серому
         спектру.  Заключительные 216 цветов представляют собой три группы
         по 72 цвета.  В каждой группе цвета плавно переходят от синего  к
         красному и зеленому. Три группы соответствуют уменьшению насыщен-
         ности или увеличению белизны.

                        Листинг 9-10. Пример режима 19 VGA.
         ---------------------------------------------------------------

         void fast19(int, int, int) ;

         main()
         {
           register i, j ;
           struct Ega_info info ;
           if(get_ega_info(&info))
              if(info.card == 'V')       /* карта VGA? */
                 set_crt_mode(19) ;
              else
                 return ;
           else
              return ;

           for(i=0x0; i<=0xFF; i++)      /* вывести палитру */
              for(j=0; j<200; j++)
              fast19(j,i,i) ;
         }
         void fast19(row,col,color)
         int row, col, color ;
         {
            /* так как каждый байт является элементом изображения,
            ** смещение элемента изображения это
            ** (строка * 320 байтов/строка) + столбец
            ** также отсутствует маска бита */
            unsigned char far *rgen = (char far *)(0xA0000000L +
                                      (row * 320) + col) ;
            *rgen = (unsigned char)color ;
         }
         --------------------------------------------------------------

             В программу 9-10 включена подпрограмма непосредственной запи-
         си точки в видеопамять, fast19(), для режима отображения 256 цве-
         тов VGA.  Поскольку каждый элемент изображения использует 1  байт
         дисплейной  памяти,  подпрограмма  может быть очень направленной.

                                      - 9-31 -
         Нет необходимости в длинных вычислениях памяти или  манипуляции с
         маской бита или маской матрицы. Похоже, что подпрограмма, возвра-
         щающая значение элемента изображения,  ведет прямо вперед. Просто
         замените оператор *rgen = color ; оператором return(*rgen) ;.


                                    Заключение

             С использованием EGA все запутано.  Фирма  IBM  разрабатывала
         EGA  для  поддержки двух совершенно разных стандартов отображения
         (CGA и MDA). Результат в настоящее время поддерживается еще более
         сложным режимом VGA.  Лучшим способом создания программного обес-
         печения,  работающего с EGA или VGA и  не  жертвующего  совмести-
         мостью,  является разделение аппаратно-зависимого кода в логичес-
         ки-независимые функции. Например, подпрограмму fastdot() програм-
         мы 9-5 легко переписать для нового режима отображения  256 цветов
         VGA.   Более   сложную   подпрограмму  вывода,  которая  вызывает
         fastdot() для вычерчивания точек,  не надо переписывать,  так как
         fastdot() поддерживает новый режим.
             В этой главе рассмотрено несколько основных графических функ-
         ций:  line(), fastdot(), readdot() и rect(). Обсуждены такие осо-
         бенности  EGA,  как регистры-защелки.  Также показаны три способа
         установки цвета EGA - регистр маски  матрицы,  регистр   установ-
         ки/сбора и режим записи 2.  Несмотря  на то,   что указанные под-
         программы работают быстро, их можно усовершенствовать. Высокопро-
         изводительные графические подпрограммы, использующие EGA или VGA,
         будут найдены в процессе исследования.






             Глава 10. ПРОГРАММИРОВАНИЕ РАСШИРЕНИЯ ЧИСЛОВОЙ ОБРАБОТКИ
                                    ФИРМЫ INTEL

             NPX с точки зрения программиста
             Использование средств MS-DOS с NPX
             Примеры программирования NPX с помощью MASM
             Краткое содержание


             Мир MS-DOS принадлежит исключительно Intel.  Это дает пользо-
         вателям MS-DOS два преимущества. Во-первых, программы, написанные
         для MS-DOS, переносимы даже на уровне объектных кодов. Во-вторых,
         большинство систем MS-DOS могут использовать чипы расширения чис-
         ловой обработки 8087,  80287 или 80387 фирмы Intel.  Во всей этой
         главе  мы  будем ссылаться на расширение числовой обработки с по-
         мощью аббревиатуры NPX.  Задачей NPX является обеспечение семейс-
         тва процессоров 8086, систем 80286 и 80386 способностью выполнять
         быстрые вычисления с плавающей запятой.
             NPX обеспечивает  систему командами для выполнения преобразо-
         ваний чисел,  основных математических действий, а также некоторых
         трансцендентных функций, таких как синус, косинус и логарифм.
             Возможности NPX не ограничиваются  только  скоростью  работы.
         Посредством того,   что составляет библиотеку математических под-
         программ с  плавающей  запятой,  NPX избавляет программиста от их
         написания, повышая таким образом скорость программирования. Кроме
         того,  так как эти подпрограммы содержатся в чипе NPX, а не в па-
         мяти программы,  использование NPX может  привести  к  уменьшению
         размера программы, что снижает стоимость некоторых разработок.
             В отличие  от  ранних  математических процессоров,  таких как
         Intel 8231 и 8232, доступ к NPX осуществляется посредством управ-
         ляющих последовательностей,  которые могут показаться программис-
         ту, работающему на языке ассемблера, командами на машинном языке.
         NPX не требует установки какого-либо дополнительного программного
         обеспечения (также как 8088 или главный чип центрального  процес-
         сора  конфигурирован  в "максимальном режиме"),  и не требует для
         доступа программного ввода/ вывода или передачи DMA.
             Ввиду того, что NPX полностью совместим с предлагаемыми стан-
         дартами IEEE (Институт инженеров по электротехнике и радиолектро-
         нике)  для  вычислений  с плавающей запятой,  доступна обширная и
         расширяющаяся база передового программного обеспечения для число-
         вых  вычислений.  Эта  база соответствует способу обработки чисел
         NPX. Для программиста, у которого нет времени для написания слож-
         ных  числовых подпрограмм,  это программное обеспечение сохраняет
         время и деньги.
             Применение 8087 NPX с процессорами 8086 и 8088 не ограничено.
         8087 NPX также может быть использовано  с  процессорами  80186  и
         80188.  Для пользователей процессора 80286 фирма Intel предостав-
         ляет 80287 NPX.  А пользователям  процессора  80386  предлагается
         80387 NPX.  Обратите внимание,  что некоторые системы, основанные
         на 80386, имеют гнездо для подключения 80287 NPX в дополнение или
         вместо  гнезда для 80387,  так как 80287 был дешевле и доступнее,
         чем 80387 NPX в то время, когда процессор 80386 был только объяв-
         лен. Несмотря  на это, информация, представленная в данной главе,
         имеет силу для всех этих комбинаций.

                                      - 10-2 -

                          NPX с точки зрения программиста

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

                               Регистры данных в NPX

             Несмотря на то, что команды NPX появляются как часть основно-
         го набора команд процессора,  NPX не имеет доступа к основным ре-
         гистрам центрального процессора.  Вместо этого NPX обладает своим
         собственным набором регистров и связывается с центральным процес-
         сором посредством общей памяти. Это не является ограничением вви-
         ду того,  что основные  регистры  центрального  процессора  плохо
         приспособлены для работы с вещественными числами. Вместо 16-бито-
         вых или 32-битовых регистров,  используемых центральным процессо-
         ром, NPX имеет восемь 80-битовых регистров и поэтому может содер-
         жать значительно больше  информации.  Эти  регистры  показаны  на
         рисунке 10-1.
          Слово     Регистры
         признака    данных
          ЪДД¬    ЪДДДДДДДДДД¬    ЪДДДДДДДДДДДДДДДДДД¬
          ¦ 0¦    ¦   ST(0)  ¦    ¦Cлово состояния   ¦
          ГДДґ    ГДДДДДДДДДДґ    АДДДДДДДДДДДДДДДДДДЩДоступны посредством
          ¦ 1¦    ¦   ST(1)  ¦                        операндов NPX
          ГДДґ    ГДДДДДДДДДДґ    ЪДДДДДДДДДДДДДДДДДД¬
          ¦ 2¦    ¦   ST(2)  ¦    ¦Cлово управления  ¦
          ГДДґ    ГДДДДДДДДДДґ    АДДДДДДДДДДДДДДДДДДЩ
          ¦ 3¦    ¦   ST(3)  ¦
          ГДДґ    ГДДДДДДДДДДґ
          ¦ 4¦    ¦   ST(4)  ¦                        Доступно только
          ГДДґ    ГДДДДДДДДДДґ    ЪДДДДДДДДДДДДДДДДДД¬посредством FSAVE
          ¦ 5¦    ¦   ST(5)  ¦    ¦Указатель команды ¦Д¬    и FSTENV
          ГДДґ    ГДДДДДДДДДДґ    АДДДДДДДДДДДДДДДДДДЩ ¦
          ¦ 6¦    ¦ ST(6)    ¦                         ¦Указатели главной
          ГДДґ    ГДДДДДДДДДДґ    ЪДДДДДДДДДДДДДДДДДД¬ ¦общей памяти CPU
          ¦ 7¦    ¦   ST(7)  ¦    ¦Указатель операнда¦ДЩ
          АДДЩ    АДДДДДДДДДДЩ    АДДДДДДДДДДДДДДДДДДЩ

                       Рис.10-1. Размещение регистров в NPX

             Вы должны помнить,  что в отличие от центрального процессора,
         регистры данных NPX не имеют уникальных имен, но они являются ин-
         дексированными элементами стека (например,  ST(1)).  Значения за-
         гружаются в NPX посредством помещения их в стек, а некоторые зна-
         чения (но не все)  восстанавливаются  путем  выталкивания  их  из
         стека.  Многие  команды  NPX работают только на вершине стека,  а
         большинство других команд установлены по умолчанию для  работы на
         вершине стека.
             То, что NPX обращается к своим регистрам как к  стеку,  очень
         важно, ввиду того, что все адреса регистров относительны к верши-
         не стека!  Например, значение, содержащееся в регистре i хранится
         в  регистре i-1,  если стек вытолкнут,  и в регистре i+1,  если в
         стек помещается новое значение.

                                      - 10-3 -
             При написании программ для NPX,  уделяйте особое внимание по-
         ведению стека. Вы не можете вставить значение в регистр и предпо-
         ложить,  что  позже  это значение будет находиться в том же самом
         месте.


             Представление в NPX вещественных чисел с плавающей точкой

             Эти регистры  также отличаются от регистров центрального про-
         цессора тем,  что они могут содержать только один тип числа,  ве-
         щественное  число  с  плавающей точкой (называемое на языке Intel
         временным вещественным). Самый верхний формат на рисунке 10-2 по-
         казывает, как в регистре NPX выглядит вещественное число с плава-
         ющей точкой.  Из рисунка видно, что регистр разделен на три поля:
         бит знака,  смещенный порядок (15 бит) и значащая часть числа (64
         бита).  Каждое из этих чисел, взятое отдельно, представляет собой
         двоичное целое без знака,  но   соединенные вместе,  они выражают
         очень большое число!
             Давайте посмотрим  внимательнее на отдельные части веществен-
         ного числа с плавающей точкой. Самая левая часть (бит 79) являет-
         ся битом знака.  Когда он имеет значение 1,  то число отрицатель-
         ное.  Когда он имеет значение 0,  то число положительное. Просто,
         но надо отметить два момента.  Во-первых,  в отличие от двоичного
         дополнения целых чисел (используемых в главном  центральном  про-
         цессоре), это вещественное число с плавающей запятой имеет столь-
         ко положительных чисел, сколько и отрицательных. (Вы увидите  это
         позже).  Во-вторых,  более важно то, что данная система счисления
         имеет два типа нуля! Это означает, что 0 может быть положительным
         или  отрицательным  числом,  и что 0 не обязательно равен 0.  NPX
         следит за этим эффектом,  но об этом надо помнить, если Вы пытае-
         тесь сравнить вещественные числа с помощью центрального процессо-
         ра.(Вам это может никогда не понадобиться, так как NPX сравнивает
         числа очень хорошо).
             Перейдя к правой стороне числа,  мы увидим мантиссу (биты с 0
         по 63).  Здесь представлены значащие цифры числа.  Так как каждый
         элемент может быть либо положительным, либо отрицательным, диапа-
         зон для каждого один и тот же. Обратите внимание, что бит 63 (са-
         мый значимый бит мантиссы) показан как 1.  Это  происходит  из-за
         того,  что NPX обычно хранит числа в нормализованном формате, ко-
         торый означает,  что NPX находит самую левую 1 в двоичном числе и
         сдвигает вверх или вниз до тех пор, пока 1 не окажется в бите 63.
         (Число без 1 означает 0, и его представлением являются все нули.)
         Приведем небольшой пример с числом 10:

         Десятичное:                                                 10
         Шестнадцатиричное:                                           A
         Двоичное 64-битовое целое:   000000000000000...000000000001010
         64-битовое вещественное NPX: 101000000000000...000000000000000

             Видите, как скользит влево число в NPX? Это дает больше места
         для других представляемых чисел,  таких как 10.1, 10.12 и так да-
         лее.  Единственной проблемой является то,  что число в NPX больше
         не 10.  Оно выглядит теперь как 10 х 2 .  Как NPX узнает, что это
         число действительно 10?  Оно использует то,  что называется полем
         экспоненты (биты с 64 по 78).
             NPX всегда предполагает,  что число в мантиссе  располагается
         между 1 и 2.  Само по себе число, показанное выше, будет двоичным

                                      - 10-4 -
         1.01 или десятичным 1.25.  (Каждая двоичная цифра в дробной части
         представляет собой 1/2 предыдущей двоичной цифры,  поэтому, пози-
         циями вправо десятичной точки в двоичном исчислении являются 1/2,
         1/4,  1/8,  1/16,  и  т.д.)  NPX  запоминает в поле экспоненты на
         сколько позиций сдвинуто первоначальное число. В случае числа 10,
         NPX сдвигает десятичную точку на три позиции от 1010.0 (двоичное)
         к 1.0100 (двоичное).  Значение 3 хранится в поле экспоненты. Есть
         еще одна хитрость в хранении данных NPX.  Так как экспонента хра-
         нится как целое без знака,  и если NPX помещает точную экспоненту
         в поле, то невозможно будет сохранить числа, меньше 1 (отсутствие
         отрицательной экспоненты означает невозможность чисел меньше, чем
         2 или единица).  Таким образом,  NPX смещает (добавляет смещение)
         экспоненту.  В NPX используется смещение 3FFFh, или 16,383 (деся-
         тичное). В примере сохранения числа 10, смещение экспоненты опре-
         деляется как 3 плюс 3FFFh, или 4002h.

                           ЪДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         Вещественное с    ¦  ¦Смещенный по-¦       Мантисса             ¦
         плавающей запятой ¦Зн¦рядок 3FFFh  ¦1                           ¦
                           АДДБДДДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                           79             64 63                          0
                                             АДДД1.0 задана явно
                           ЪДДВДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
           Упакованное     ¦  ¦Не использу-¦  Упакованные десятичные     ¦
           десятичное      ¦Зн¦   ется     ¦17 16 15   цифры   3  2  1  0¦
                           АДДБДДДДДДДДДДДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДЩ
                           79            72 71                           0

                                        ЪДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДД¬
                         Длинное        ¦  ¦Смещенный по-¦ Мантисса      ¦
                         вещественное   ¦Зн¦рядок 3FFFh  ¦               ¦
                                        АДДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДЩ
                                        63              52АД1.0
                                                           подразумевается
                                        ЪДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
                         Длинное        ¦  ¦Представление целого в допол-¦
                         целое          ¦Зн¦      нительном коде         ¦
                                        АДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                                        63                               0

                                                 ЪДДВДДДДДДДВДДДДДДДДДДДД¬
                                   Короткое      ¦  ¦Порядок¦  Мантисса  ¦
                                   вещественное  ¦Зн¦  7Fh  ¦            ¦
                                                 АДДБДДДДДДДБДДДДДДДДДДДДЩ
                                                 31        23АД1.0       0
                                                           подразумевается
                                                 ЪДДВДДДДДДДДДДДДДДДДДДДД¬
                                      Короткое   ¦  ¦   Дополнительный   ¦
                                      целое      ¦Зн¦       код          ¦
                                                 АДДБДДДДДДДДДДДДДДДДДДДДЩ
                                                 31                      0
                                                           ЪДДВДДДДДДДДДД¬
                                               Целое-      ¦  ¦Дополните-¦
                                               -слово      ¦Зн¦льный код ¦
                                                           АДДБДДДДДДДДДДЩ
                                                           15            0
                        Рис.10-2. Представление чисел в NPX

                                      - 10-5 -

             Мы все сделали,  теперь посмотрим на рисунок 10-3, чтобы уви-
         деть,  как число 10 выглядит внутри NPX. Почему Вы должны понять,
         как NPX  хранит  числа?  Потому что иногда в процессе отладки Вам
         требуется проверить содержимое регистров NPX и, в целях понимания
         использования  и  ограничений  некоторых более продвинутых команд
         NPX, необходимо знать типы данных, которыми Вы манипулируете.

            79 78 ... 64 63 62    ............     0
           ЪДВДДДДДДВДДВДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
           ¦0¦ 40 02¦ 1¦  010    000    000    000 ¦
           АДБДДДДДДБДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                    

            +   2(3) x1.010=1010(Binary)=10(Decimal)


                      Рис.10-3. Представление числа 10 в NPX


                     Другие форматы данных, используемые в NPX

             На рисунке 10-2 кроме 80-битового формата вещественного числа
         с плавающей запятой показаны шесть других  форматов  данных.  Для
         чего они применяются?  Вместе с 80-битовым вещественным NPX может
         использовать эти формы для чтения и записи  вещественных  данных.
         Если  эти  данные  представлены в одном из этих форматов,  то NPX
         поймет их.  Другие форматы NPX не воспринимает. Три основных типа
         показаны на рисунке 10-2.  Эти типы включают в себя вещественное,
         целое и упакованное десятичное.


                   Короткий вещественный и длинный вещественный
                                  форматы данных

             Короткий вещественный (32-битовый) и длинный вещественный (64
         -битовый)  форматы очень похожи на 80-битовый вещественный с пла-
         вающей запятой,  который мы уже рассмотрели.  Эти числа  способны
         представлять вещественные числа с плавающей запятой, но с меньшим
         диапазоном и точностью. Различия показаны в таблице 10-1.
                                                         Таблица 10-1
                   Различия между форматами вещественных данных
         ДДДДДДДДДДДДВДДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДД
         Тип данных  ¦Мантисса¦ Экспонента¦ Смещение    ¦Самая левая
                     ¦ # бит  ¦   # бит   ¦ экспоненты  ¦единица
         ДДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДДДДДДД†ДДДДДДДДДДДДДДД
                     ¦        ¦           ¦             ¦
         80-битовый  ¦        ¦           ¦             ¦
         вещественный¦  64    ¦   15      ¦ 3FFF(16383) ¦Определена
         64-битовый  ¦        ¦           ¦             ¦
         вещественный¦  52    ¦   11      ¦ 3FF(1023)   ¦Предполагается
         32-битовый  ¦        ¦           ¦             ¦
         вещественный¦  23    ¦    8      ¦ 7F(127)     ¦Предполагается
         ДДДДДДДДДДДДБДДДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДД

             Кроме размера короткий и длинный вещественные форматы отлича-
         ются от 80-битового вещественного тем, что самый значимый бит ре-

                                     - 10-6 -
         ально не появляется!  Из-за своего ограниченного пространства эти
         форматы всегда предполагают 1 в самой левой позиции, но не хранят
         1, таким образом они приобретают позицию для другой цифры.


                    Целое слово, короткий целый и длинный целый
                                  форматы данных

             Формы целого хорошо известны.  Эти формы  используются  цент-
         ральным  процессором для хранения дополнения двоичных целых чисел
         (хотя центральный процессор не может применять  формат 8-битового
         длинного целого). Эти числа имеют следующие диапазоны:
             64-битовый:
             от -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807
             32-битовый:
             от -2,147,483,648 до 2,147,483,647
             16-битовый:
             от -32,768 до 32,767

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


                Форматы упакованного двоично-десятичного кода (BCD)

             Последней формой представления данных в NPX  является  упако-
         ванный BCD (двоично-десятичный код). Что такое упакованный BCD? В
         системе счисления двоично-десятичного кода каждый 4-битовый полу-
         байт представляет собой отдельную цифру, которая может иметь зна-
         чение между 0 и 9.  Все число представляет  собой  только  строку
         цифр.  Таким  образом,  число  больше  похоже  на строку символов
         ASCII.  На рисунке 10-4 показано представление числа 256  в  нор-
         мальной двоичной форме и форме двоично-десятичного кода.  Неболь-
         шие вычисления проводятся в десятичной системе счисления.

                  ЪДДВДДДДДДВДДДДД¬             ЪДДВДДДДДДВДДДДД¬
                  ¦01¦ 0000 ¦ 0000¦             ¦01¦ 0000 ¦ 0000¦
                  АДДБДДДДДДБДДДДДЩ             АДДБДДДДДДБДДДДДЩ
                  9-8  7...4 3...0              9-8  7...4 3...0
                                                        
                   1     0     0                 2      5    6

         Двоичный:1x256 + 0x16 + 0x1 = 256     2x256 + 5x16 + 6x1 = 598
         Двоично-
         десятич.:1x100 + 0x10 + 0x1 = 100     2x100 + 5x10 + 6x1 = 256

              Рис.10-4. Представление числа в двоично-десятичном коде

             Из рисунка 10-4 видно,  что мы пишем числа в двоично-десятич-
         ном коде,  как если бы они были шестнадцатиричными (одна цифра на
         каждый 4-битовый полубайт),  но интерпретируем их как десятичные.
         Но почему эта форма данных так важна?  Потому, что она служит для
         преобразований  между  ASCII и упакованным BCD.  Рисунок 10-5 де-
         монстрирует,  что для перехода из BCD в ASCII  Вы  должны  только

                                      - 10-7 -
         распаковать цифры (одну на полубайт) в байты и прибавить шестнад-
         цатиричное 30 для формирования символов ASCII от 0 до 9 (шестнад-
         цатиричные с 30 по 39).  Для выполнения обратного  преобразования
         надо вычесть 30h из каждого символа и упаковать  их,  по  два  на
         байт.

                               ЪДДВДДДДДДВДДДДД¬
                               ¦01¦ 0000 ¦ 0000¦
                               АДДБДДДДДДБДДДДДЩ
                               9-8  7...4 3...0
            Двоично-десятичный            
                 формат         2     5     6
                                ¦     ¦     ¦
                                ¦     ¦     ¦
                     ЪДДДДДДДДДДЩ     ¦     АДДДДДДДДДД¬
                     ¦                ¦                ¦
                                                     
            ЪДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДД¬
            ¦ 0011      0010 ¦ 0011      0101  ¦ 0011      0110  ¦
            АДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДЩ
                  7.......0       7........0        7........0
                      ¦               ¦                  ¦
                                                       
                   32(Hex)         35(Hex)            36(Hex)
                      ¦               ¦                  ¦
                      АДДДДДДД¬       ¦        ЪДДДДДДДДДЩ
            Формат ASCII                     


                Рис.10-5. Преобразования между числами ASCII и BCD

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


                              Коротко о типах данных

             В таблице 10-2 мы рассмотрели  размер  чисел,  которые  могут
         быть  представлены  каждым  типом данных вместе с приблизительным
         десятичным разрешением (количество значащих цифр), поддерживающим
         каждый  тип данных.  Для реального использования мы можем пореко-
         мендовать следующее: Использовать упакованный BCD для перехода от
         чисел  ASCII к вещественным числам с плавающей запятой и обратно.
         Использовать вещественные числа с плавающей запятой для всех  вы-

                                      - 10-8 -
         числений  и  для представления в MASM констант вещественных чисел
         (мы рассмотрим это позже). А также использовать форму минимально-
         го целого, пригодную для числа, выражающего константу целого чис-
         ла в MASM.  Следуя этим указаниям мы  можем  получить  наибольшую
         точность  с  некоторым сохранением объема памяти путем применения
         где возможно формы меньшего целого.
             Рисунок 10-6  демонстрирует  диапазон  представления  числа в
         NPX.  Обратите внимание, что NPX хранит числа с большей точностью
         внутри  (80-битовое вещественное),  чем при загрузке и сохранении
         регистров NPX (длинное вещественное). Это дает дополнительный за-
         пас точности для вычислений. Отметьте также, что пробел между от-
                                                         Таблица 10-2
                       Диапазон и точность типов данных NPX
         ДДДДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Тип данных  ¦ Двоичные¦Десятичные¦ Приблизительный диапазон
                     ¦ биты    ¦цифры     ¦
         ДДДДДДДДДДДД†ДДДДДДДДД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Вещественное¦         ¦          ¦
         с плавающей ¦         ¦          ¦
         запятой     ¦  80     ¦  19      ¦ 3.4х10(-4932)<N<1.2х10(4932)
         Упакованное ¦         ¦          ¦
         десятичное  ¦  80     ¦  18      ¦ -10(18)-1< N < 10(18) -1
         Длинное     ¦         ¦          ¦
         вещественное¦  64     ¦  15-16   ¦ 4.19х10(-307)<N<1.67х10(308)
         Длинное целое  64     ¦  18      ¦ -9х10(18) < N < +9х10(18)
         Короткое    ¦         ¦          ¦
         вещественное¦  32     ¦   6-7    ¦ 8.43х10(-37)<N<3.37х10(38)
         Короткое    ¦         ¦          ¦
         целое       ¦  32     ¦   9      ¦ -2х10(9) < N < +2х10(9)
         Целое слово ¦  16     ¦   4      ¦ -32,768 < N < +32,767
         ДДДДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

          І Внешний диапазон внутреннего представления

          ° Внешний диапазон длинного вещественного


                        Основной              Основной
                        диапазон              диапазон
                       2(64) - 2           2(64) - 2
                   Уникальное значение   Уникальное значение
                        #'s                  #'s
      - беско-   -8  -4  -2  -1 -1/2  0  1/2  1   2   4   8     + беско-
        нечность          ЪДДД¬               ЪДДД¬             нечность
                   Exp Exp Exp Exp         Exp Exp Exp Exp
                    2   1   0   1           1   0   1   2
           ДДДДґГД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДДДДДДґГДДДДД
           І°Д¬                   ЪД°І¦І°Д¬                          ЪД°І
           ІД¬ -1.67x10(308)     ¦ ЪІ¦І¬ ¦             +1.67x10(308)ЪДІ
             ¦          -4.19x10(-307) +4.19x10(-307)               ¦
             -1.2x10(4932)         ¦   ¦                  +1.2x10(4932)
                       -3.4x10(-4932)  +3.4x10(-4932)

         Плотность чисел                Плотность чисел
                      ДДДДДДДДДДДДДДД>             ДДДДДДДДДДДДДДДДДДДД>
         уменьшается к 0                увеличивается к бесконечности

                   Рис.10-6. Диапазон представления чисел в NPX

                                      - 10-9 -
         дельными представляемыми числами (расстояние между двумя смежными
         числами,  которые NPX может представить точно) уменьшается к нулю
         (в обоих направлениях) и увеличивается к бесконечности  (плюс или
         минус).  Эта плотность представления числа подразумевает, что NPX
         с большей точностью обрабатывает очень  маленькие  числа,  нежели
         большие.

                                 Набор команд NPX

             NPX имеет то,  что в индустрии называется богатым набором ко-
         манд. Это не обязательно означает, что имеется много команд (хотя
         он имеет 69 различных команд),  а то,  что  набор  команд  хорошо
         приспособлен для выполнения требуемых операций. Существуют коман-
         ды почти для каждой операции, что значительно сокращает количест-
         во шагов (и соответствующих трудностей при программировании), ко-
         торые могут встретиться при использовании менее мощного числового
         сопроцессора.
             В таблице 10-3 приведены 69 команд.  Таблица организована  по
         типам  выполняемых операций,  а не в алфавитном порядке,  так как
         предпочтительнее искать команду по типу, чем по имени. Необходимо
         пояснить два обозначения,  присутствующие в таблице 10-3. Сначала
         отметку (P), появляющуюся в некоторых командах. Это означает, что
         соответствующая  команда  может  быть  использована  в форме POP,
         FopP. Форма POP указывает NPX, что необходимо увеличить указатель
         стека  и определить регистр прежней вершины стека как пустой, что
         существенно понижает вершину стека.  Все это становится ясным да-
         лее.


                                   Префикс FWAIT

             Второе обозначение в таблице 10-3 - это отметка  (N). Отметка
         (N) означает, что соответствующая команда может быть использована
         в форме no-wait,  как в FNop.  Обычно ассемблер  MASM  генерирует
         префикс FWAIT для каждой команды NPX. Форма no-wait указывает ас-
         семблеру MASM,  что надо генерировать префикс FWAIT.  Так что  же
         такое префикс FWAIT?
             Обычно NPX должен ожидать завершения текущей  команды,  перед
         тем  как  он  примет новую.  Это достигается посредством префикса
         FWAIT  кода  операции  (9В  шестнадцатиричное),  который  реально
         представляет собой код  операции  8086!  Выполняя  эту  операцию,
         главный центральный процессор ждет, когда контакт TEST интерфейса
         центральный процессор/NPX станет активным.  Это происходит, когда
         NPX,  завершив выполнение команды,  ожидает новую.  Главный цент-
         ральный процессор вновь приступает к работе и вызывается  следую-
         щая команда NPX, начиная новый цикл.
             Причина использования FWAIT в качестве префикса заключается в
         том,  что главный центральный процессор ждет только тогда,  когда
         хочет направить NPX другую команду.  Как только новая команда по-
         сылается NPX, центральный процессор и NPX могут работать одновре-
         менно,  и,  когда центральный процессор вновь нуждается в NPX, он
         должен проверить готовность последнего.
             Есть еще  один  случай,  когда  главный центральный процессор
         нуждается в использовании команды FWAIT.  Всякий раз,  как только
         центральному процессору необходимо прочитать данные NPX, он посы-

                                      - 10-10 -
         лает соответствующую команду записи данных в память. Главный про-
         цессор должен ждать  (посредством команды FWAIT),  пока данные не
         будут доступны. В этом случае программист должен однозначно зако-
         дировать команду NPX FWAIT,  потому что MASM не знает, какое уст-
         ройство ждет завершения выполнения команды - центральный  процес-
         сор или NPX.
                                                         Таблица 10-3
                    Перечень команд Intel NPX и формы адресации
         ДДДДДДДДВДДДДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         .       ¦Мнемоника ¦Способ     ¦          Имя команды
         Отметки ¦команды   ¦адресации  ¦
         ДДДДДДДДБДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

         .               Команды пересылки данных (9)
         -------------------------------------------------------------
         .       ¦FXCH      ¦  //d      ¦Обмен содержимого регистров
         .       ¦FLD       ¦  s        ¦Загрузить вещественное
         (P)     ¦FST       ¦  d        ¦Сохранить вещественное
         .       ¦FILD      ¦  s        ¦Загрузить целое
         (P)     ¦FIST      ¦  d        ¦Сохранить целое
         .       ¦FBLD      ¦  s        ¦Загрузить упакованный BCD
         .       ¦FBSTP     ¦  d        ¦Сохранить упакованный BCD
         --------------------------------------------------------------
         .                 Команды констант (7)
         --------------------------------------------------------------
         .       ¦FLDZ      ¦           ¦Загрузить +0.0
         .       ¦FLD1      ¦           ¦Загрузить +1.0
         .       ¦FLDPI     ¦           ¦Загрузить Pi
         .       ¦FLDL2T    ¦           ¦Загрузить log 10 по основанию 2
         .       ¦FLDL2E    ¦           ¦Загрузить log e по основанию 2
         .       ¦FLDLG2    ¦           ¦Загрузить log 2 по основанию 10
         .       ¦FLDLN2    ¦           ¦Загрузить log 2 по основанию e
         --------------------------------------------------------------
         .            Трансцендентные команды (8)
         --------------------------------------------------------------
         .       ¦FPTAN     ¦           ¦Относительный тангенс
         .       ¦FPATAN    ¦           ¦Относительный арктангенс
         .       ¦F2XM1     ¦           ¦2(x) - 1
         .       ¦FYL2X     ¦           ¦Y x log X по основанию 2
         .       ¦FYL2XP1   ¦           ¦Y x log (X + 1) по основанию 2
         .       ¦FCOS      ¦           ¦Косинус ST(0) (только 80387)
         .       ¦FSIN      ¦           ¦Синус ST(0) (только 80387)
         .       ¦FSINCOS   ¦           ¦Синус и косинус ST(0)
         .       ¦          ¦           ¦(только 80387)
         --------------------------------------------------------------
         .                 Команды сравнения (10)
         --------------------------------------------------------------
         (P)     ¦FCOM      ¦  //s      ¦Сравнить вещественные
         (P)     ¦FICOM     ¦  s        ¦Сравнить целые
         .       ¦FCOMPP    ¦           ¦Сравнить и выполнить POP дважды
         .       ¦FTST      ¦           ¦Проверить вершину стека
         .       ¦FXAM      ¦           ¦Просмотреть вершину стека
         .       ¦FUCOM     ¦           ¦Сравнить вне порядка
         .       ¦          ¦           ¦(только 80387)
         .       ¦FUCOMP    ¦           ¦Сравнить вне порядка и
         .       ¦          ¦           ¦выполнить POP (только 80387)
         .       ¦FUCOMPP   ¦           ¦Сравнить вне порядка и

                                      - 10-11 -
         .                               выполнить POP дважды
         .       ¦          ¦           ¦(только 80387)
         --------------------------------------------------------------
         .                Арифметические команды (26)
         --------------------------------------------------------------
         (P)     ¦FADD      ¦  *        ¦Сложить вещественные
         .       ¦FIADD     ¦  s        ¦Сложить целые
         (P)     ¦FSUB      ¦  *        ¦Вычесть вещественное
         .       ¦FISUB     ¦  s        ¦Вычесть целое
         (P)     ¦FSUBR     ¦  *        ¦Вычесть вещественное (обратное)
         .       ¦FISUBR    ¦  s        ¦Вычесть целое (обратное)
         (P)     ¦FMUL      ¦  *        ¦Несколько вещественных
         .       ¦FIMUL     ¦  s        ¦Несколько целых
         (P)     ¦FDIV      ¦  *        ¦Разделить вещественное
         .       ¦FIDIV     ¦  s        ¦Разделить целое
         (P)     ¦FDIVR     ¦  *        ¦Разделить вещественное
         .       ¦          ¦           ¦(обратное)
         .       ¦FIDIVR    ¦  s        ¦Разделить целое (обратное)
         .       ¦FSQRT     ¦           ¦Квадратный корень
         .       ¦FSCALE    ¦           ¦Масштаб
         .       ¦FPREM     ¦           ¦Частичный остаток
         .       ¦FPREM1    ¦           ¦Частичный остаток
         .       ¦          ¦           ¦(IEEE, только 80387)
         .       ¦FRNDINT   ¦           ¦Округлить до целого
         .       ¦FXTRACT   ¦           ¦Выделить экспоненту и мантиссу
         .       ¦FABS      ¦           ¦Абсолютное значение
         .       ¦FCHS      ¦           ¦Сменить знак
         --------------------------------------------------------------
         .          Команды управления обработкой (16)
         --------------------------------------------------------------
         (N)     ¦FINIT     ¦           ¦Инициализировать процессор
         .       ¦FLDCW     ¦  s        ¦Загрузить управляющее слово
         (N)     ¦FSTCW     ¦  d        ¦Сохранить управляющее слово
         (N)     ¦FSTSW     ¦  d        ¦Сохранить слово состояния
         #(N)    ¦FSTENV    ¦  d        ¦Сохранить среду
         .       ¦FLDENV    ¦  s        ¦Загрузить среду
         #(N)    ¦FSAVE     ¦  d        ¦Сохранить состояние
         .       ¦FRSTOR    ¦  s        ¦Восстановить состояние
         .       ¦FINCSTP   ¦           ¦Увеличить SP
         .       ¦FDECSTP   ¦           ¦Уменьшить SP
         .       ¦FFREE     ¦  d        ¦Освободить регистр
         .       ¦FNOP      ¦           ¦Нет операции
         .       ¦FWAIT     ¦           ¦Центральный процессор ждет
         (N)     ¦FDISI     ¦           ¦Запретить прерывания
         .       ¦          ¦           ¦(только 8087)
         (N)     ¦FENI      ¦           ¦Разрешить прерывания
         .       ¦          ¦           ¦(только 8087)
         (N)     ¦FCLEX     ¦           ¦Сбросить ситуацию
         ДДДДДДДДБДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          * Формы командных операндов для FADD, FSUB, FMUL, FDIV, FDIVR
             :F<op>       ... генерирует F<op>P ST(1),ST
             :F<op>s      ... генерирует F<op> ST,<память>
             :F<op> d,s   ... только регистры d,s
             :F<op>P d,s  ... только регистры d,s

             (P)  Формы F<op> или F<op>P
             (N)  Формы F<op> или FN<op>

                                      - 10-12 -
             s    Источник
             d    Адресат
             //s  Ничего или источник
             //d  Ничего или адресат
             #    Команда не является самосинхронизируемой

                               Способы адресации NPX

             Способы адресации NPX отражают архитектуру  стека процессора.
         Все  числовые  коды операций NPX,  в отличие от управляющих кодов
         операций,  используют вершину стека как,  по крайней  мере,  один
         операнд. Некоторые команды работают только с  вершиной стека, на-
         пример, FSQRT и FABS. Другие работают как с вершиной стека, так и
         со следующим регистром стека, например, FSCALE и F2XM1. Оставшие-
         ся двухоперандные команды изменяются в соответствии с типом.  Не-
         которые берут свой второй операнд из другого регистра стека. Дру-
         гие могут брать свой второй операнд из памяти.
             В таблице  10-4 продемонстрированы различные разрешенные ком-
         бинации адресации операндов и команд NPX.  Обратите внимание, что
         хотя некоторые математические команды и команды сравнения исполь-
         зуют операнд памяти в качестве источника, операнды памяти никогда
         не  могут применяться в качестве адресата кроме команд сохранения
         (FST<P>,  FIST<P> и FBSTR). Отметьте также, что операнд источника
         для любой целой команды (FIop) должен быть операндом памяти пото-
         му, что регистры NPX всегда содержат вещественные числа.
                                                         Таблица 10-4
                     Разрешенные типы для числовых команд NPX
         ДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                  ¦                    ВТОРЫЕ ОПЕРАНДЫ
                  ГДДДДДДВДДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДВДДДДДДДДДДДДД
         Пример   ¦      ¦       ¦Двухмер-¦        ¦       ¦ Команды
         команды  ¦Слово ¦Двойное¦ное     ¦ Десять ¦Регистр¦ математ. и
         NPX      ¦      ¦слово  ¦слово   ¦ байтов ¦NPX    ¦ сравнения
         ДДДДДДДДД†ДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДД†ДДДДДДДДДДДДД
                  ¦      ¦       ¦        ¦        ¦       ¦
         FLD      ¦      ¦       ¦        ¦        ¦       ¦
         исто-    ¦      ¦       ¦        ¦        ¦       ¦ Веществен-
         чник     ¦      ¦ Да    ¦  Да    ¦  FLD   ¦ Да    ¦ ное
         FST      ¦      ¦       ¦        ¦        ¦       ¦
         адресат  ¦      ¦ Да    ¦  Да    ¦  FSTP  ¦       ¦ Нет
         FILD     ¦      ¦       ¦        ¦        ¦       ¦
         источник ¦ Да   ¦ Да    ¦  Да    ¦        ¦       ¦ Целое
         FIST     ¦      ¦       ¦        ¦        ¦       ¦
         адресат  ¦ Да   ¦ Да    ¦  Да    ¦        ¦       ¦ Нет
         FBLD     ¦      ¦       ¦        ¦        ¦       ¦
         источник ¦      ¦       ¦        ¦  Да    ¦       ¦ Нет
         FBSTP    ¦      ¦       ¦        ¦        ¦       ¦
         адресат  ¦      ¦       ¦        ¦  Да    ¦       ¦ Нет
         ДДДДДДДДДБДДДДДДБДДДДДДДБДДДДДДДДБДДДДДДДДБДДДДДДДБДДДДДДДДДДДДД
             Некоторые сомнения возникают по поводу того,  как NPX обраща-
         ется к своим операндам.  Небольшой пример поможет развеять туман,
         поэтому  давайте посмотрим на работу трех кодов операций NPX.

         FLD   <arg1>  ; Загрузить 1-ый аргумент из памяти
         FLD   <arg2>  ; Загрузить 2-ой  аргумент  из  памяти
         FADD  <arg2>  ;  кодируется  как FADD ST(1),ST
         FSTP <result> ; сохранить результат в памяти

                                      - 10-13 -

             Эта операция использует FLD для считывания двух операндов па-
         мяти в стек регистра NPX,  складывает их, применяя "классическую"
         форму FADD,  и затем сохраняет результат с помощью FSTP. Помните,
         что в то время как основные арифметические команды  (FADD,  FSUB,
         FMUL и FDIV) кодируются сами по себе,  MASM генерирует классичес-
         кую операцию снятия со стека,  используя вершину стека  ST, в ка-
         честве источника и следующий элемент стека ST(1) в качестве адре-
         сата.
             Работа четырех  предыдущих  команд представлена графически на
         рисунке 10-7.  Мы разделили две части команды FADD таким образом,
         чтобы  Вы  могли  лучше видеть эффекты снятия со стека.  Гладя на
         операцию,  Вы можете увидеть,  что  NPX  концептуально  завершает
         арифметическую  ее  часть  -  сохраняя результата в ST(1) - затем
         снимает со стека,  перенося результат в  вершину  стека,  ST  или
         ST(0).
                                      Одна и та же команда
                                     ЪДДДДДДДДДДДДДДДДДДДДДДД¬
                                         FADDP       FADDP    FSTP сохра-
              FLD помещает в стек:      (ST(1)=   выталкивает няет и вы-
                 Mem-1       Mem-21   Mem-1 + Mem-2   Mem-2  талкивает SUM
              ЪДДДДДДДДДД¬ЪДДДДДДДДДД¬ЪДДДДДДДДДД¬ЪДДДДДДДДДД¬ЪДДДДДДДДДД¬
         ST(0)¦   Mem-1  ¦¦  Mem-2   ¦¦  Mem-2   ¦¦  SUM     ¦¦Предыдущее¦
              ГДДДДДДДДДДґГДДДДДДДДДДґГДДДДДДДДДДґГДДДДДДДДДДґГДДДДДДДДДДґ
         ST(1)¦Предыдущее¦¦  Mem-1   ¦¦  SUM     ¦¦Предыдущее¦¦          ¦
              ГДДДДДДДДДДґГДДДДДДДДДДґГДДДДДДДДДДґГДДДДДДДДДДґГДДДДДДДДДДґ
         ST(2)¦          ¦¦Предыдущее¦¦Предыдущее¦¦          ¦¦          ¦
              АДДДДДДДДДДЩАДДДДДДДДДДЩАДДДДДДДДДДЩАДДДДДДДДДДЩАДДДДДДДДДДЩ

                       Рис.10-7. Пример работы со стеком NPX
             В конце  нашего  примера  стек  принимает свой первоначальный
         вид.  Так ли это? Да, это так, если в стеке было место для допол-
         нительных аргументов. Однако, если в нем не было достаточно места
         для размещения новых данных,  NPX объявляет о недопустимой ситуа-
         ции в работе ввиду переполнения стека.  (Мы рассмотрим чрезвычай-
         ные ситуации в дальнейшем.) Следовательно, до того, как мы сможем
         выполнить даже нашу очень простую задачу,  необходимо убедиться в
         том, что NPX может принимать данные. Для достижения этого сущест-
         вует два способа.


                               Команды FINIT и FFREE

             Простейшим способом подготовки NPX к работе является  исполь-
         зование команды FINIT.  Это первая команда,  которую нужно напра-
         вить NPX всякий раз как только начинает выполняться  новая  прог-
         рамма.  FINIT  инициализирует  NPX  таким  образом,  как  если бы
         произошел сброс всей системы, что означает очистку всех регистров
         и ситуаций, а также обеспечивает свободное место для работы прог-
         раммиста.
             Другую возможность убедиться в том, что NPX освободил регист-
         ры, предоставляет команда FFREE. Она отмечает необходимый регистр
         как пустой и позволяет программисту использовать его для последу-
         ющих вычислений. Обратите внимание, что нет необходимости очищать
         регистры на вершине стека.  Если нижняя часть стека, ST(7), имеет
         достаточно свободного места,  верхние регистры при добавлении но-
         вого значения опустятся вниз.

                                      - 10-14 -


                                  Управление NPX

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

                               Слово управления NPX

             Сначала мы рассмотрим регистр слова управления.  Это 16-бито-
         вое слово определяет то,  как NPX трактует различные ситуационные
        ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
        ¦ 15¦ 14¦ 13¦ 12¦ 11¦ 10¦  9¦  8¦  7¦  6¦  5¦  4¦  3¦  2¦  1¦  0¦
        ГДДДБДДДБДДД†ДДД†ДДДБДДД†ДДДБДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДДґ
        ¦           ¦   ¦       ¦       ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦
        ¦           ¦ IC¦   RC  ¦   PC  ¦IEM¦   ¦ PM¦ UM¦ OM¦ ZM¦ DM¦ IM¦
        АДДДБДДДБДДДБДДДБДДДДДДДБДДДДДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
        АДДДДДДДДДДДЩ ¦ АДДДДДДДБДДДДДДДЩ ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦
      зарезервировано ¦     ¦       ¦     ¦   ¦   ¦   ¦   ¦   ¦   ¦   
                      ¦     ¦       ¦     ¦   ¦   ¦   ¦   ¦   ¦   ¦Неверная
      УправлениеЪДДДДДЩ     АДДД¬   ¦     ¦   ¦   ¦   ¦   ¦   ¦   операция
  бесконечностью               ¦   ¦     ¦   ¦   ¦   ¦   ¦   ¦Ненормализо-
        ЪДДДДДДДДДДДДДДДДДДДДД¬ ¦   ¦     ¦   ¦   ¦   ¦   ¦   ¦ванный опе-
        ¦0 = Сходящаяся       ¦ ¦   ¦     ¦   ¦   ¦   ¦   ¦         ранд
        ¦  Ъ бесконеч.¬       ¦ ¦   ¦     ¦   ¦   ¦   ¦    Деление на ноль
        ¦  ¦          ¦       ¦ ¦   ¦     ¦   ¦   ¦    Переполнение
        ¦- ¦          ¦ +     ¦ ¦   ¦     ¦   ¦     Потеря значимости
        ¦  АДДД 0 ДДДДЩ       ¦ ¦   ¦     ¦      Точность         Маска
        ¦1 = Расходящаяся     ¦ ¦   ¦     ¦Зарезервирован      разрешения
        ¦                     ¦ ¦   ¦     ¦                    прерывания
        ¦-беск.<-- 0 -->+беск.¦ ¦   ¦     ¦  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
        АДДДДДДДДДДДДДДДДДДДДДЩ ¦   ¦     АДД¦0= Разрешить                ¦
                                ¦   ¦        ¦1= Запретить (замаскировано)¦
                                ¦   ¦        АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
      Управление   ЪДДДДДДДДДДДДЩ   АДДДДДДДДДДДД¬ Управление
      округлением                                точностью
        ЪДДДДДДДДДДДДДДДДДДДДД¬      ЪДДДДДДДДДДДДДДДДДДДДДДДД¬
        ¦00=(x+1)<---0.5--->x ¦      ¦            ЪДДДДДДДДДД¬¦
        ¦01=     ---> 0 --->  ¦      ¦00=         ¦ 24 бита  ¦¦
        ¦10=     <--- 0 <---  ¦      ¦            АДДДДДДДДДДЩ¦
        ¦11=     ---> 0 <---  ¦      ¦       ЪДДДДДДДДДДДДДДД¬¦
        АДДДДДДДДДДДДДДДДДДДДДЩ      ¦10=    ¦    53 бита    ¦¦
                                     ¦       АДДДДДДДДДДДДДДДЩ¦
                                     ¦    ЪДДДДДДДДДДДДДДДДДД¬¦
                                     ¦11= ¦    64 бита       ¦¦
                                     ¦    АДДДДДДДДДДДДДДДДДДЩ¦
                                     АДДДДДДДДДДДДДДДДДДДДДДДДЩ
             Рис.10-8. Слово управления и его влияние на операции NPX

                                      - 10-15 -
         условия и то, как он видит используемую систему счисления. На ри-
         сунке  10-8  продемонстрирована работа слова управления,  а также
         различные поля и их воздействия. В основном, слово управления со-
         держит три поля управления и семь флагов для использования с осо-
         быми ситуациями.
             На данном этапе мы хотим использовать как можно больше встро-
         енных средств NPX. Часть этого означает то, что мы хотим восполь-
         зоваться встроенными возможностями обработки особых ситуаций NPX.
         Вы  видите,  что  NPX  само по себе может следить за большинством
         возникающих ошибок,  исправлять число или возвращать  специальное
         значение, называемое не-число.  Так как обрабатывать эти прерыва-
         ния вручную сложно, мы предоставим NPX делать это за нас. Мы про-
         делаем это посредством маскировки особой ситуации, что выполняет-
         ся установкой масок ситуаций в слове управления. Все маски особых
         ситуаций вместе с главной маской разрешения прерываний содержатся
         в младшем байте слова управления.
             Установка NPX в режим использования своих внутренних обработ-
         чиков ошибок выполняется установкой младшего байта в BF (шестнад-
         цатиричное)  с  применением  команды  загрузки  слова  управления
         FLDCW.  Мы просто определяем слово в памяти главного центрального
         процессора с помощью младшего байта,  имеющего значение BF (шест-
         надцатиричное). Затем мы загружаем его следующим образом:
             .
             .
             .
           cw87    dw    03BFh    ; значение слова управления NPX
                   .
                   .
                   .
                  FLDCW  cw87     ; загрузить слово управления NPX
                   .
                   .
                   .
             Зачем мы применили значение 3 для старшего байта слова управ-
         ления?  Старший байт содержит три поля для определения используе-
         мой NPX модели числа.  Эти три поля  также  показаны  на  рисунке
         10-8.  Сравнив диаграмму с нашим значением 3,  Вы увидите, что мы
         выбрали 64-битовую точность,  округление до ближайшего  целого  и
         сходящуюся бесконечность.  Эти значения единственные, рекомендуе-
         мые фирмой Intel, а также единственные, которые NPX использует по
         умолчанию.  Если  Вы хотите изменить эти установки,  рисунок 10-8
         подскажет, какие значения можно применять.


                                Слово состояния NPX

             Слово состояния NPX содержит четыре типа информации:  (1) ин-
         дикатор занятости; (2) указатель вершины стека; (3) коды условий,
         отражающие результаты выполнения команд FCOM,  FTST и FXAM; и (4)
         индикаторы особых ситуаций, сигнализирующие о возможности появле-
         ния ошибки.  Рисунок 10-9 демонстрирует положения различных инди-
         каторов внутри слова состояния.

                                     - 10-16 -
       ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДД¬
       ¦ 15¦ 14¦ 13¦ 12¦ 11¦ 10¦  9¦  8¦  7¦  6¦  5¦  4¦  3¦  2¦  1¦  0¦
       ГДДД†ДДД†ДДДБДДДБДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДДґ
       ¦   ¦   ¦           ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦
       ¦ B ¦ C3¦     ST    ¦ C2¦ C1¦ C0¦ IR¦   ¦ PE¦ UE¦ OE¦ ZE¦ DE¦ IE¦
       АДДДБДДДБДДДДДДДДДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
         ¦   ¦ АДДДДДДДДДДДБДДДДДДДДДДДЩ ¦   ¦   ¦   ¦   ¦   ¦   ¦   ¦
         ¦   АДДДДДДДДДДДДДДДДДДД¦       ¦   ¦   ¦   ¦   ¦   ¦   ¦   
                    ¦           ¦       ¦   ¦   ¦   ¦   ¦   ¦   ¦Неверная
       Занято        ¦                  ¦   ¦   ¦   ¦   ¦   ¦   операция
       Указатель     ¦         Коды      ¦   ¦   ¦   ¦   ¦   ¦Ненормализо-
       на вершину    ¦       условий     ¦   ¦   ¦   ¦   ¦   ¦ванный опе-
         стека                          ¦   ¦   ¦   ¦   ¦   ¦      ранд
        ЪДДДДДДДДДДДДДДДДДДДДДДД¬        ¦   ¦   ¦   ¦   ¦   
        ¦          ЪДДДДДДДДДДД¬¦        ¦   ¦   ¦   ¦    Деление на ноль
        ¦000 ----> ¦ Регистр 0 ¦¦        ¦   ¦   ¦    Переполнение
        ¦001 ----> ¦ Регистр 1 ¦¦        ¦   ¦     Потеря значимости
        ¦010 ----> ¦ Регистр 2 ¦¦        ¦      Точность         Маска
        ¦011 ----> ¦ Регистр 3 ¦¦        ¦Зарезервирован      разрешения
        ¦100 ----> ¦ Регистр 4 ¦¦        ¦                    прерывания
        ¦101 ----> ¦ Регистр 5 ¦¦        
        ¦110 ----> ¦ Регистр 6 ¦¦        Требование прерывания
        ¦111 ----> ¦ Регистр 7 ¦¦
        ¦          АДДДДДДДДДДДЩ¦
        АДДДДДДДДДДДДДДДДДДДДДДДЩ
                           Рис.10-9. Слово состояния NPX

             Индикатор занятости сигнализирует о том,  выполняет ли в  на-
         стоящее время NPX обработку команды.  Этот индикатор не очень по-
         лезен для нас,  так как содержимое слова состояния не может  быть
         использовано до тех пор, пока NPX не объявит, что он завершил за-
         пись слова состояния.  Исходя из этого, Вы знаете, что NPX свобо-
         ден, так как команда FWAIT выполнена.
             Указатель вершины  стека,  в  битах  с 11 по 13,  полезен для
         программиста,  который пишет сложные служебные программы NPX, вы-
         полняющие  последовательные операции и сохраняющие многочисленные
         значения в стеке NPX. В этих случаях, для того, чтобы убедиться в
         том,  что для следующей операции имеется достаточно места,  перед
         обработкой программы проверьте глубину стека.  Если в стеке недо-
         статочно места для поддержки операции, некоторые или все регистры
         должны быть сохранены в памяти для того,  чтобы появилась возмож-
         ность выполнить программу без нежелательных последствий.
             Указатель стека инициализируется  командой  FINIT  для  того,
         чтобы  указывать на 000(0),  а каждая успешно загруженная команда
         уменьшает указатель стека циклически возвращая 111(7) до тех пор,
         пока  он не достигнет 001(1).  Указателем стека можно манипулиро-
         вать с помощью  команд  FINCSTP  (увеличить  указатель  стека)  и
         FDECSTP  (уменьшить указатель стека).  Тем не менее,  так как эти
         операции не отмечают регистры как  пустые,  использование  команд
         FDECSTP или FINCSTP может запретить применение индикатора вершины
         стека для проверки свободных регистров.
             Коды условий  необходимы  чаще всего для того,  чтобы решить,
         какое действие предпринять в пункте принятия  решения  программы.
         Далее  мы рассмотрим несколько примеров использования кодов усло-
         вий.  Короче говоря,  для проверки кодов условий,  запишите слово
         состояния в память с помощью команды FSTSW;  затем проверьте коды
         с помощью главного центрального процессора. При сохранении инфор-

                                      - 10-17 -
         мации состояния NPX для проверки центральным процессором не забы-
         вайте добавлять  команду  FWAIT  после команды записи.  Следующий
         фрагмент программы демонстрирует,  как может появиться последова-
         тельность сравнения.
               .
               .
               .
         sw87  dw    ?          ; пространство для слова состояния NPX
               .
               .
               .
               FCOM  ST(1)      ; проверить отношение между  ST и ST(1)
               FSTSW sw87       ; записать слово состояния NPX
               FWAIT            ; ждать завершения работы
               test  sw87,4000h ; операнды равны?
               je    are_equl   ; да...
                .
                .
                .
             Значения, присвоенные этим кодам различными командами сравне-
         ния,  приведены в таблице 10-5. Обратите внимание, что коды усло-
         вий не попадают в одну группу,  но делятся  на  части  указателем
         стека, и что коды, возвращаемые командами FCOM и FTSR, также раз-
         деляются битом условия С1, который не используется. Отметьте, что
         NAN означает "не-число".
                                                         Таблица 10-5
         Условия состояния, установленные командами FCOM, FTST и FXAM
         ДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДД
                       ¦      КОДЫ УСЛОВИЙ     ¦
            Команда    ГДДДДДДДДДДДДДДДДДДДДДДДґ      Результат
                       ¦    C3   C2  C1  C0    ¦
         ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДД
                       ¦                       ¦
         .      F      ¦    0    0   *   0     ¦  ST > источника
         .      C      ¦    0    0   *   1     ¦  ST < источника
         .      O      ¦    1    0   *   0     ¦  ST = источник
         .      M      ¦    1    1   *   1     ¦  ST ? источник
         .      F      ¦    0    0   *   0     ¦  ST > 0.0
         .      T      ¦    0    0   *   1     ¦  ST < 0.0
         .      S      ¦    1    0   *   0     ¦  ST = 0.0
         .      T      ¦    1    1   *   1     ¦  ST ? 0.0
         .      F      ¦    0    0   0   0     ¦  + Ненормализованное
         .      X      ¦    0    0   0   1     ¦  + NAN
         .      A      ¦    0    0   1   0     ¦  - Ненормализованное
         .      M      ¦    0    0   1   1     ¦  - NAN
         .             ¦    0    1   0   0     ¦  + Нормализованное
         .             ¦    0    1   0   1     ¦  + Бесконечность
         .             ¦    0    1   1   1     ¦  - Бесконечность
         .             ¦    1    0   0   0     ¦  + 0
         .             ¦    1    0   0   1     ¦    Пусто
         .             ¦    1    0   1   0     ¦  - 0
         .             ¦    1    0   1   1     ¦    Пусто
         .             ¦    1    1   0   0     ¦  + Денормализованное
         .             ¦    1    1   0   1     ¦    Пусто
         .             ¦    1    1   1   0     ¦  - Денормализованное
         .             ¦    1    1   1   1     ¦    Пусто
         ДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДД
             (*) - Не существенно

                                      - 10-18 -


                          Обработка особых ситуаций в NPX

             Младший байт  слова состояния содержит флаги особых ситуаций.
         Эти флаги соответствуют маскам ситуаций в слове управления. Когда
         имеет  место  особая ситуация,  NPX устанавливает соответствующий
         флаг и затем проверяет,  замаскирована эта ситуация или нет.  Так
         как  большинство  операций применяют замаскированный ответ (внут-
         ренние обработчики прерываний NPX), мы свели их действие в табли-
         цу  10-6. Не забывайте о периодической проверке на наличие особых
         ситуаций в целях обеспечения точности результатов.  Если  имеется
         особая ситуация,  то устанавливается соответствующий флаг и стоит
         до тех пор,  пока он не будет сброшен  посредством  инициализации
         NPX (команда FINIT) или путем использования команды сброса ситуа-
         ции FCLEX. Так как флаги остаются установленными, они обеспечива-
         ют кумулятивную запись любых ошибок, возникающих в процессе обра-
         ботки.
             Другой способ обработки особых ситуаций заключается в размас-
         кировании одной или более ситуаций и разрешении прерываний в сло-
         ве управления NPX.  В этом режиме,  если NPX обнаруживает  особую
         ситуацию,  он сигнализирует прерыванием и требует,  чтобы главный
         центральный процессор обработал ситуацию.  Тем не менее,  NPX  не
         всегда  связан с линией запроса прерывания центрального процессо-
         ра! Для размещения запросов прерываний NPX требуется внешняя схе-
         ма  обработчика  прерываний.  Если  Ваша  система не поддерживает
         внешние прерывания NPX, то не разрешайте их!
             Если Ваша система поддерживает внешние прерывания и Вы разре-
         шили их,  то необходимо обеспечить обработчик особых ситуаций,  в
         то время,  когда NPX прерывает работу главного центрального  про-
         цессора.  Для  определения  причины  возникшей проблемы программа
         центрального процессора должна прочитать слово состояния NPX. Ес-
         ли  Вы потребуете,  то Ваш обработчик ситуаций тоже может опреде-
         лить команду и операнд, приведшие к возникновению проблемы, путем
         исследования  указателей  команды  и операнда NPX.  Для получения
         этой информации обработчик ситуаций должен выполнить одну из  ко-
         манд NPX, FSTENV или FSAVE. Эти команды записывают в память глав-
         ного центрального процессора по меньшей мере содержание пяти  ре-
         гистров управления NPX (слово состояния,  слово управления, слово
         признака, указатель команды и указатель операнда). Обработчик си-
         туаций  может  восстановить информацию из памяти и обработать ее.
         Если Вы хотите получить более полное представление  об  этих  ре-
         гистрах,  то изучите листинг 10-1 раздела "Примеры программирова-
         ния NPX с помощью MASM",  содержащий  пример  программы,  которая
         сначала сохраняет эту информацию, а затем декодирует ее.
                                                         Таблица 10-6
                       Установленные по умолчанию ответы на
                   особые ситуации NPX. (Ситуации замаскированы)
         ДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Особая ситуация   ¦         Замаскированный ответ
         ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Точность             ¦  Возвращает округленный результат
         Потеря значимости    ¦  Денормализованный результат
         Переполнение         ¦  Возвращает бесконечность со знаком
         Деление на ноль      ¦  Возвращает бесконечность со знаком
         .                    ¦  операнда

                                      - 10-19 -
         ДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Особая ситуация   ¦          Замаскированный ответ
         ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Денормализованный    ¦  Если операнд памяти, то игнорируется
         операнд              ¦  Если операнд регистра, то переводит в
         .                    ¦  "ненормализованное" и оценивает снова
         Неверная операция    ¦  Если один операнд NAN, то возвращает
         .                    ¦  его. Если оба операнда NAN, то возвра-
         .                    ¦  щает тот,  который имеет большее абсо-
         .                    ¦  лютное значение. Если ни один из опе-
         .                    ¦  рандов не является NAN, то возвращает
         .                    ¦  неопределенность
         ДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                        Использование средств MS-DOS с NPX

             Единственное отличие написания программ с использованием NPX
         от написания программа без его использования заключается  в  том,
         что  применение  сопроцессора  требует большего количества команд
         для применения числовых операций. Так как отличие видно только на
         уровне  команд,  единственными  средствами MS-DOS,  которым нужно
         знать о NPX,  являются MASM и DEBUG.  Все другие средства - LINK,
         LIB и CREF - игнорируют наличие NPX.

                             Использование MASM и NPX

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

         - Только смещение                  FSTSW      mem_word
         - Только база или индекс           FIADD      word prt [bx]
         - Смещение + база или индекс       FSTP       base[di]
         - База + индекс                    FLDCW      [bp][si]
         - Смещение + база + индекс         FILD       [bp]table[di]

           ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
           ¦                        ВНИМАНИЕ                          ¦
           ¦      MASM  версии 1.25 имеет ошибку,  которая приводит к ¦
           ¦ обмену кодов  операций FSUB c FSUBR  или  FDIV c FDIVR и ¦
           ¦ наоборот, если любой из них использован в "классической" ¦
           ¦ форме  (без определения операндов).  Если Вы пользуетесь ¦
           ¦ более старой версией MASM,  точно определяйте операнды и ¦
           ¦ тип этих команд, например:                               ¦
           ¦ FSUBP    ST(1),ST                                        ¦
           ¦ FDIVRP   ST(1),ST                                        ¦
           ¦   Помните,  что классическая  форма всегда использует    ¦
           ¦   форму выталкивания команды из стека.                   ¦
           АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

                                      - 10-20 -

                         NPX переключатели MASM - /r и /s

             После записи  программы в файл,  необходимо использовать MASM
         для ее трансляции.  Если применяется стандартная командная строка
         MASM,  то  каждая  встреченная  команда NPX выдает синтаксическую
         ошибку. Это происходит из-за того, что в режиме нормальной работы
         MASM ничего не знает о NPX. Для трансляции команд NPX надо приме-
         нять переключатель командной строки /r (реальный режим), что ука-
         зывает MASM на наличие в исходном файле команд NPX:

         A:>masm test.asm test.obj test.lst test.crf/r

             Благодаря этому  MASM  узнает,  что  транслируемая  программа
         предназначена для выполнения с реальным NPX. MASM затем генериру-
         ет  правильные коды операций NPX с префиксом кода операции FWAIT,
         если не используется одна из команд FN<op>.  (Тем не менее, обра-
         тите  внимание,  что хотя команда NPX FNOP начинается с FN,  MASM
         генерирует префикс FWAIT.)
             MASM имеет  еще один переключатель,  который указывает ему на
         необходимость трансляции команды NPX. Это переключатель /e (режим
         эмуляции).  Переключатель /e почти идентичен переключателю реаль-
         ного режима,  единственное отличие заключается в том, что команды
         no-wait (FN<op>) не транслируются. Этот переключатель нужен поль-
         зователям,  имеющим эмуляционные библиотеки, которые для эмуляции
         программ могут заменять коды операций NPX вызовами главного цент-
         рального процессора.  Так как MASM не  имеет  такую  эмуляционную
         библиотеку и нет необходимости в ее использовании, если Вы имеете
         реальное NPX, то мы больше не будем обсуждать эту тему.


                              Типы данных NPX в MASM

             Вы теперь  знаете,  что NPX поддерживает семь различных типов
         данных:  слово;  короткое и длинное целое; короткое и длинное ве-
         щественное;  упакованный двоично-десятичный код; и вещественное с
         плавающей запятой.  Для использования этих типов данных в  памяти
         должны быть определены правильные ячейки. В таблице 10-7 показано
         соответствие между типами данных NPX и методами,  применяемыми  в
         MASM для их определения и обращения к ним.
             Ячейки памяти распределяются с помощью  директив  определения
         данных MASM (dw,  dd, dq, или dt) и знака вопроса (?), следующего
         за ними.  Этот формат приказывает MASM зарезервировать пространс-
         тво,  но не инициализировать его.  Для инициализации зарезервиро-
         ванной ячейки, предназначенной для отдельного значения веществен-
         ного    числа,    MASM    обеспечивает   три   различных   формы:
         экспоненциальный формат без экспоненты, экспоненциальный формат с
         экспонентой и реальную (R) форму.  Каждая из этих форм может быть
         использована с любой из  больших  директив  "определения  данных"
         следующим образом:

         double   dd  3.14159         ; экспоненциальный без экспоненты
         quad     dq  1.23456E + 03   ; экспоненциальный с экспонентой
         tenbyte  dt  0123456789ABCDEF0123R     ; реальный

                                      - 10-21 -
                                                         Таблица 10-7
                         Сравнение типов данный NPX и MASM
         ДДДДДДДДДДДДДВДДДДДДДДДДДВДДДДДДДВДДДДДДДВДДДДДДДДДДВДДДДДДДДДДД
         Тип данных   ¦Тип данных ¦ Размер¦ Дирек-¦   Имя    ¦Совмести-
         NPX          ¦ главного  ¦   в   ¦ тива  ¦операнда  ¦мость с
                      ¦   ЦП      ¦ байтах¦ MASM  ¦          ¦NPX
         ДДДДДДДДДДДДД†ДДДДДДДДДДД†ДДДДДДД†ДДДДДДД†ДДДДДДДДДД†ДДДДДДДДДДД
         Целое слово  ¦ Слово     ¦   2   ¦  dw   ¦ word prt ¦   Да
         Короткое     ¦ Двойное   ¦       ¦       ¦          ¦
         целое        ¦ слово     ¦   4   ¦  dd   ¦ dword ptr¦   Да
         Короткое     ¦ Двойное   ¦       ¦       ¦          ¦
         вещественное ¦ слово     ¦   4   ¦  dd   ¦ dwort ptr¦   Нет
         Длинное      ¦ Четверное ¦       ¦       ¦          ¦
         целое        ¦ слово     ¦   8   ¦  dq   ¦ qword ptr¦   Да
         Длинное      ¦ Четверное ¦       ¦       ¦          ¦
         вещественное ¦ слово     ¦   8   ¦  dq   ¦ qword ptr¦   Нет
         Упакованный  ¦ Десятый   ¦       ¦       ¦          ¦   Форма
         BCD          ¦ байт      ¦   10  ¦  dt   ¦ tbyte ptr¦    "R"
         Плавающее    ¦ Десятый   ¦       ¦       ¦          ¦
         вещественное ¦ байт      ¦   10  ¦  dt   ¦ tbyte ptr¦   Да
         ДДДДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДБДДДДДДДБДДДДДДДДДДБДДДДДДДДДДД

             Определение вещественных чисел с  помощью  байта  определения
         (db)  или директив определения слова (dw) не возможно.  Они могут
         быть инициализированы только в целые значения.
             Экспоненциальные форматы  оцениваются  в  формате с плавающей
         запятой (знак,  экспонента и  мантисса),  поскольку  вещественный
         формат  используется  по  основанию  цифра-на-полубайт,   поэтому
         шестнадцатиричное представление вещественного формата точно соот-
         ветствует его определению.
             Обратите внимание,  что хотя MASM может определять веществен-
         ные числа как 4-битовой, так и 8-битовой длины, формат, использу-
         емый для инициализации этих чисел, не совместим с NPX! На рисунке
         10-10  показано,  как  Microsoft  реализует  числа этих размеров.
         Сравнивая их с рисунком 10-2,  Вы увидите, что они совсем разные.
         Если Вам необходимо использовать эти форматы (например,  для сов-
         местимости с существующим программным обеспечением), то для пере-
         вода  одного формата в другой Вы можете написать программу преоб-
         разования.
                 Длинное ЪДДДДДВДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
            вещественное ¦ 81h ¦S¦ Мантисса 55 бит ¦                     ¦
                         АДДДДДБДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                          63 56 55 54
                            ¦    АДДД 1.0 предполагается
                            ¦
                            ¦
          Короткое          ¦    Смещенная    ЪДДДДДВДВДДДДДДДДДДДДДДДДДД¬
          вещественное      АДДДэкспонентаДДДД¦ 81h ¦S¦  Мантисса 23 бита¦
          в Microsoft MASM          129.0     АДДДДДБДБДДДДДДДДДДДДДДДДДДЩ
                                               31  24 23 22
                                                      АД1.0 предполагается

               Рис.10-10. Форматы вещественных чисел Microsoft MASM.

                             Использование DEBUG с NPX

             DEBUG всегда знает о командах NPX.  Этим  объясняет  то,  что

                                      - 10-22 -
         иногда  при  попытке  "дисассемблирования"  памяти,  DEBUG выдает
         странные команды.  (Одним из методов,  используемых при  отладке,
         является  заполнение неиспользуемой памяти шестнадцатиричным сло-
         вом DEAD.  Этот характерный образец позволяет программисту быстро
         определить,  сколько памяти изменено.  Тем не менее, DEBUG дисас-
         семблирует это как FISUBR WORD PTR [DI + ADDE].)
             Хотя программа DEBUG постоянно находится в режиме NPX, она не
         всегда распознает команды NPX. Программа не выводит на экран и не
         позволяет  транслировать  команды формы FN<op>.  DEBUG распознает
         FWAIT как отдельную команду кода операции NPX,  чем она реально и
         является.  Следовательно,  DEBUG  декодирует  команду  FN<op> как
         стандартную команду, которая не будет иметь префикса FWAIT.
             В противоположность  MASM,  DEBUG  не вставляет автоматически
         префикс FWAIT в стандартные команды NPX. Вы должны помнить о том,
         что необходимо вручную транслировать FWAIT при вводе команды NPX.
             Вы должны также помнить,  что  при  определении  операндов  в
         DEBUG, требуется указывать программе, какой размер имеет операнд:

         FLD    TBYTE PTR [200]

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

                               Отладка регистров NPX

             Единственное, что DEBUG не может сделать,  так это отобразить
         состояние NPX или содержание любого из его регистров. Если Вы ре-
         шили  проверить какие-либо регистры NPX,  то сначала надо сделать
         так, чтобы NPX записал данные в общую память.
             Для того, чтобы помочь Вам отлаживать программы NPX, мы пред-
         лагаем программу dump87,  содержащуюся в следующем разделе, "При-
         меры программирования NPX с помощью MASM".  Эта программа исполь-
         зует  команду  FSAVE  для  записи  состояния  NPX  с  последующим
         отображением его в более понятной форме на экране дисплея.  Прог-
         ЪДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДД ДД¬
         ¦ Байт младшего адреса  ¦  Байт старшего адреса ¦0,1,2 байта/   ¦
         ГДДВДДВДДВДДВДДВДДДДДВДД†ДДДДДВДДВДДДДДВДДДДДДДД†ДДДДДДДДДД \ \ ¦
      (1)¦ 1¦ 1¦ 0¦ 1¦ 1¦ OP-A¦ 1¦ aa  ¦ 1¦ OP-B¦  mmm   ¦Смещение   / / ¦
         ГДД†ДД†ДД†ДД†ДД†ДДДДД†ДД†ДДДДД†ДДБДДДДД†ДДДДДДДД†ДДДДДДДДД  \ \ ¦
      (2)¦ 1¦ 1¦ 0¦ 1¦ 1¦ **  ¦ *¦ aa  ¦  OP-B  ¦  mmm   ¦Смещение   / / ¦
         ГДД†ДД†ДД†ДД†ДД†ДДВДД†ДД†ДДВДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДДДД \ \ДЩ
      (3)¦ 1¦ 1¦ 0¦ 1¦ 1¦ R¦ P¦ *¦ 1¦ 1¦  OP-B  ¦   m    ¦
         ГДД†ДД†ДД†ДД†ДД†ДДВДД†ДД†ДДВДД†ДДВДДДДДБДДДДДДДДґ
      (4)¦ 1¦ 1¦ 0¦ 1¦ 1¦ 0¦ 0¦ 1¦ 1¦ 1¦ 1¦      OP      ¦
         ГДД†ДД†ДД†ДД†ДД†ДДВДД†ДД†ДДВДД†ДД†ДДДДДДДДДДДДДДґ
      (5)¦ 1¦ 1¦ 0¦ 1¦ 1¦ 0¦ 1¦ 1¦ 1¦ 1¦ 1¦      OP      ¦
         АДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДДДДДДДДДДДДДЩ
           7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
         АДДДДДДДДДДДДДДБДДДДДДДДЩ
            Переход      Код NPX
                             ¦  OP-A и OP-B ¦
                             АДДДДДэтоДДДДДДЩ
                        разделенный код операции
          ДДДДДДДДДДДДДДДДДДД
              *  - OP-A
              ** - Формат
                        Рис.10-11. Форматы кодировки команд

                                      - 10-23 -
         рамма может быть помещена в библиотеку или включена  в трансляцию
         для  вызова  ее  при появлении необходимости проверить вычисления
         NPX. Более полно программа рассматривается в следующем разделе.

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

             При чтении  шестнадцатиричных дампов команды NPX можно узнать
         по наличию либо кода операции FWAIT (9B), либо характерных управ-
         ляющих  кодов,  с D8 по DF (шестнадцатиричное).  На рисунке 10-11
         показаны различные формы, которые может принимать команда, но все
         команды начинаются с комбинации битов 11011.

                    Примеры программирования NPX с помощью MASM

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

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

                                 Программа DUMP87

             Ранее мы  отметили,  что программа DEBUG не имеет возможности
         проверить содержимое или состояние NPX. В листинге 10-1 представ-
         лена программа,  которая выполняет дамп содержания NPX и произво-
         дит его проверку.

                   Листинг 10-1. DUMP87 - cредство отладки NPX
         ----------------------------------------------------------------

         PAGE     60,132       ; широкий листинг
         #.8087                ; разрешить трансляцию команд 8087 NPX
         ;=============================================================
         ;          Р Е А Л И З А Ц И Я   Б И Б Л И О Т Е К И
         ;
                  PUBLIC  dump87   ; определена библиотечная программа
         ;
         #MODEL   SMALL
         ;
         #.CODE
                  EXTRN   bin2hex:NEAR  ; вызов библиотечной программы

                                      - 10-24 -
         ;=============================================================
         ; D U M P 8 7   -   С Р Е Д С Т В О   О Т Л А Д К И   8 0 8 7
         ;
         ; Эта процедура выполняет дамп  полного  состояния  расширения
         ; числовой обработки (NPX) фирмы Intel (8087, 80287 и 80387) в
         ; стек, затем форматирует и выводит его на экран.
         ;
         ; Требования к установке: НЕТ
         ; Требования к стеку: свободно 108 байтов стека
         ;
         ; ...wd -- Слово, определенное для полей бита различных слов.
         ; Определенные структуры получают преимущество ввиду того, что
         ; структуры прерываний SW и CW соответствуют друг другу.
         ;-------------------------------------------------------------
         ;             М А К Р О О П Р Е Д Е Л Е Н И Я
         ;
         ;
         ;;  Отобразить символ (из DL)
         @DisChr  MACRO  char
                  push   ax
                  push   dx
                  mov    dl,&char
                  mov    ah,02h
                  int    21h
                  pop    dx
                  pop    ax
                  ENDM
         ;;
         ;;  Отобразить строку по метке
         @DisStr  MACRO  string
                  push   ax
                  push   dx
                  mov    dx,offset &string
                  mov    ah,09h
                  int    21h
                  pop    dx
                  pop    ax
                  ENDM
         ;
         ;;  Отобразить строку (из DS:DX)
         @Display MACRO
                  mov    ah,09h
                  int    21h
                  ENDM
         ;
         #.DATA
         ;-------------------------------------------------------------
         ;          О П Р Е Д Е Л Е Н И Я    С Т Р У К Т У Р
         ;
         intrpt  record master:1,nul0:1,pr:1,un:1,ov:1,zd:1,de:1,inv_op:1
         comtrol record infc:1,rndc:2,prec:2
         status  record busy:1,c3:1,stp:3,c2:1,c1:1,c0:1
         tag     record onetag:2
         ipwd    record ipseg:4,nul2:1,opcode:11 ; код операции и команда
                                                ; ...указатель
         opwd  record opseg:4,nul3:12       ; сегмент операнда указателя
         expwd record sign:1,exp:15         ; знак и экспонента

                                      - 10-25 -
         ;
         ; Основная структура среды:
         enviro  STRUC
         cw87    dw  ?    ; слово управления
         cw87    dw  ?    ; слово состояния
         tw87    dw  ?    ; слово признака
         ipo87   dw  ?    ; смещение указателя команды
         ips87   dw  ?    ; сегмент указателя команды и кода операции
         opo87   dw  ?    ; смещение указателя операнда
         ops87   dw  ?    ; сегмент указателя операнда
         enviro  ENDS
         ; Структура регистра:
         fltreg  STRUC
         man87   dq  ?    ; мантисса (значащая часть)
         exp87   dw  ?    ; экспонента и знак
         fltreg  ENDS
         ;
         ; Структура сохранения состояния:
         state87 STRUC
                 db    size enviro dup (?)      ; заголовок среды
         reg87   db    size fltreg * 8 dup (?)  ; 8 регистров данных
         state87 ENDS
         ;
         dump87s STRUC         ; формат стека для
         ;                     ; ... dump 87
         rec87   db   size state87 dup (?)  ; место для состояния NPX
         ; oldbp dw   ?                  ; элемент базового указателя
         dump87s ENDS
         ;
         BASE    EQU  [bp - size dump87s]   ; индекс структуры
         ;
         #.CODE
         ;-------------------------------------------------------------
         ;         Н А Ч А Т Ь   К О Д   П Р О Г Р А М М Ы
         ;
         dump87  PROC  NEAR
                 push  bp      ; сохранить элемент базового указателя
                 pushf         ; сохранить флаги вызывающего оператора
                 push  ds      ; сохранить сегмент данных
                               ; ... вызывающего оператора
                 mov   bp,sp   ; и установить индекс
                 sub   sp,size dump87s  ; освободить пространство для
                                        ; ... локального хранения
                 push  ax      ; сохранить регистры вызывающего
                               ; ... оператора
                 push  ax
                 push  bx
                 push  cx
                 push  dx
                 push  di
                 push  si
         ;
                 mov   ax,cs  ; установить сегмент данных для
                              ; ... указания на эту
                 mov   ds,ax  ; ... область данных программы

                                      - 10-26 -
         ;
         ; Получить копию внутреннего состояния NPX:
                 pushf        ; сохранить состояние прерывания
                              ; ... вызывающего оператора
                 cli          ; запретить прерывания во время записи
                 FSAVE  BASE.rec87  ; сохранить состояния NPX
                 FRSTOR BASE.rec87  ; восстановить записанное состояние
                 FWAIT              ; ждать завершения восстановления
                 popf               ; снова разрешить прерывания?
         ;
         ; Теперь мы имеем копию состояния NPX, декодируем его и
         ; выведем пользователю на терминал.
         ;
         ;          Представление состоит из следующих пунктов
         ;
         ;    ===================== NPX DUMP ==========================
         ;    Infinity:  Affine   Round.......near    Precision: 64
         ;    Inst Addr: x:xxxx   Oper Addr: x:xxxx   Opcode: Dxxx
         ;
         ;            INT PRE UND OVR ZER DEN IIP         C3 C2 C1 C0
         ;    Enable:  x   x   x   x   x   x   x          x  x  x  x
         ;    Signal:  x   x   x   x   x   x   x <-- "x" означает неза-
         ;                                                маскированное
         ;                                                или сигнал
         ;           exponent       significand
         ;    ST(x)  + xxxx     xxxx xxxx xxxx xxxx    #0  tag
         ;     .
         ;     .
         ;     .
         ;    ---------------------------------------------------------
         ;
         ; Управление бесконечностью, округлением и точностью:
              @DisStr LINE1                 ; начать отображение
              mov   al,byte ptr BASE.cw87+1 ; получить слово управления
              and   al,mask infc            ; управление бесконечностью
              mov   cl,infc
              shr   al,cl                   ; условие #
              mul   inf_siz                 ; смещение условия
              add   ax,offset inf_cnd       ; адрес условия
              mov   dx,ax
              @Display
         ;
              @DisStr rnd_lab
              mov   al,byte ptr BASE.cw87+1 ; получить слово управления
              and   al,mask rndc            ; управление округлением
              mov   cl,rncd
              shr   al,cl                   ; управление #
              mul   rnd_siz                 ; смещение условия
              add   ax,offset rnd_cnd       ; адрес условия
              mov   dx,ax
              @Display
         ;
              @DisStr pre_lab
              mov   al,byte ptr BASE.cw87+1 ; получить слово управления
              and   al,mask prec            ; управление точностью
              mov   cl,prec

                                      - 10-27 -
              shr   al,cl                   ; управление #
              mul   pre_siz                 ; смещение условия
              add   ax,offset pre_cnd       ; адрес условия
              mov   dx,ax
              @Display
         ;
         ; Указатели команды и операнда, а также код операции
              @DisStr LINE2                 ; следующая строка
              mov   ax,BASE.ips87           ; указатель команды
              and   ax,mask ipseg           ; сегмент
              mov   cl,ipseg
              shr   ax,cl                   ; цифра
              mov   ch,1                    ; отобразить 1
              call  bin2hex
              @DisChr ':'
              mov   ax,BASE.ipo87           ; указатель команды
              mov   ch,4                    ; смещение
              call  bin2hex
         ;
              @DisStr opadr                 ; указатель операнда
              mov   ax,BASE.ops87           ; указатель команды
              and   ax,mask opseg           ; сегмент
              mov   cl,opseg
              shr   ax,cl                   ; цифра
              mov   ch,1                    ; отобразить 1
              call  bin2hex
              @DisChr ':'
              mov   ax,BASE.opo87           ; указатель операнда
              mov   ch,4                    ; смещение
              call  bin2hex
         ;
              @DisStr ocode                 ; код операции
              mov   ax,BASE.ips87
              and   ax,mask opcode
              or    ax,0800h              ; добавить бит кода операции
              mov   ch,3                  ; 3 цифры
              call  bin2hex               ; отобразить
         ;
         ; Флаги разрешения прерывания / особой ситуации:
              @DisStr LINE3                 ; следующая строка
              mov   al,byte ptr BASE.sw87   ; флаг разрешения ситуации
              call  exception_flags         ; показать состояние
         ;
         ; Коды условий:
              @DisStr space10
              mov   ah,byte ptr BASE.sw87+1 ; коды условий
              push  ax                      ; (сохранить коды)
              mov   al,30h                  ; (ASCII "0")
              and   ah,mask c3              ; C3
              sub   ah,mask c3              ; 0 -> CY, 1 -> NC
              cmc                           ; 0 -> NC, 1 -> CY
              adc   al,0                    ; 0 -> "0", 1 -> "1"
              @DisChr al                    ; отобразить
              pop   ax                      ; (сохранить коды)
         ;
              mov   ch,c2 + 1               ; # отображаемых кодов
         next_cc:

                                      - 10-28 -
              @DisStr SPACE2
         ;
              mov   al,30h                  ; (ASCII "0")
              and   ah,mask c2 + mask c1 + mask c0  ; C2
              sub   ah,mask c2              ; 0 -> CY, 1 -> NC
              cmc                           ; 0 -> NC, 1 -> CY
              adc   al,0                    ; 0 -> "0", 1 -> "1"
              @DisChr al                    ; отобразить
         ;
              shl   ah,1                    ; следующий код
              dec   ch                      ; уменьшать на 1 ...
              jnz   next_cc                 ; ... пока все не
                                            ; ... будет сделано
         ;
         ; Флаги состояния прерывания / особой ситуации:
              @DisStr LINE6
              mov   al,byte ptr BASE.sw87   ; флаг сигнала ситуации
              call  exception_flags         ; показать состояние
         ;
         ; Отобразить регистр данных:
              @DisStr LINE6
              mov   dh,8                    ; # отображаемого регистра
              mov   si,0                    ; начать с регистра #0
         ;
         register_display:
              @DisStr LINE8                 ; регистры состояния
              push    dx                    ; сохранить счет
              mov     al,8                  ; вычислить регистр #
              sub     al,dh
              add     al,30h                ; перевести в ASCII
              @DisChr al                    ; и отобразить
              pop     dx
         ;
         ; Знак регистра данных:
              @DisStr paren                 ; следующим идет знак
              mov     ax,word ptr BASE.reg87[si].exp87
              test    ax,mask sign          ; что это?
              jnz     sign_minus
              @DisStr plus
              jmp     show_exponent
         sign_minus:
              @DisStr minus
         ;
         ; Экспоненциальная часть регистра данных:
         show_exponent:
              and     ax,mask exp           ; получить экспоненту
              xor     cx,cx                 ; четыре символа
              call    bin2hex               ; и отобразить
              @DisStr space3
         ;
              mov     di,si                 ; основание регистра
              add     di,offset exp87       ; положение мантиссы
              mov     dl,4                  ; 4 слова на регистр
         ;
         ; Отобразить значащую часть регистра данных:
         show_significand:
              sub     di,2                  ; указать начало слова

                                      - 10-29 -
              mov     ax,word ptr Base.reg87[di]
              call    bin2hex               ; и отобразить
              @DisStr SPACE1
              dec     dl
              jnz     show_significand
         ;
         ; Правильный номер регистра:
              @DisStr truenum
              mov     al,byte ptr BASE.sw87+1 ; получит указатель стека
              and     al,mask stp
              mov     cl,stp
              shr     al,cl                 ; иметь указатель стека
         ;
              mov     cl,8                  ; преобразовать счетчик в
              sub     cl,dh                 ; ... значение от 0 до 7
              add     al,cl                 ; # текущего регистра
         ;
              push    ax                    ; сохранить номер регистра
              add     al,30h                ; преобразовать в ASCII
              @DisChr al                    ; и отобразить
         ;
              @DisStr SPACE2                ; переход в поле TAG
         ;
         ; Состояние слова признака:
              mov     ax,BASE.tw87          ; получить слово признака
              pop     cx                    ; получить номер регистра
                                            ; ... в CL
              shl     cl,1                  ; многократно по 2
              shr     ax,cl                 ; получить соответствующее
                                            ; ... слово признака
              and     ax,mask tag
         ;
              push    dx
              mul     tag_siz               ; смещение условия
              add     ax,offset tag_cnd     ; адрес условия
              mov     dx,ax
              @Display                    ; показать состояние признака
              pop     dx
         ;
         ; Для данного регистра все выполнено!
              add     si,size fltreg        ; следующий регистр
              dec     dh                    ; меньше на 1
              jz      finished
              jmp     register_display      ; пока все не выполнено
         ;
         ; Все выполнено для всех регистров!
         ;
         finished:
              @disStr LINE9                 ; все сделано!
         ;
         ; Восстановить главный центральный процессор в первоначальное
         ; состояние. Начать с сохраненных регистров.
              pop     si                    ; восстановить регистры...
                                            ; ... вызывающего оператора
              pop     di
              pop     dx
              pop     cx

                                      - 10-30 -
              pop     bx
              pop     ax
              mov     sp,bp               ; восстановить стек
              pop     ds                  ; восстановить сегмент данных
              popf                        ; восстановить флаги...
                                          ; ... вызывающего оператора
              pop     bp                  ; восстановить весь...
                                          ; ... базовый указатель
              ret                         ; вернуться после завершения
         ;
         ;-------------------------------------------------------------
         ; Отобразить подпрограмму для вывода на экран состояния маски
         ; и сигнала особых ситуаций.
         ; Проверить байт в AL на наличие битов, соответствующих
         ; флагам особых ситуаций.
         ;
         exception_flags PROC   NEAR
              test    al,mask master      ; главное управление
              call    mark_it
         ;
              mov     cl,pr               ; следующим идет флаг PR
              ror     al,cl               ; перейти к первой позиции
              inc     cl                  ; считать 1 > бита #
         ;
         test_exception:
              test    al,1                ; флаг установлен?
              call    mark_it
              rol     al,1                ; следующий шаг
              dec     cl                  ; следить за счетом
              jnz     test_exception      ; продолжать до завершения
              ret
         ;
         ;-------------------------------------------------------------
         ; Отметить результат в соответствии с установленными флагами
         ; записи.
         ;
         mark_it PROC NEAR
              jz      mark_space
              @DisStr marky
              ret
         mark_space:
              @DisStr markn
              ret
         mark_it ENDP
         ;
         exception_flags ENDP
         ;
         #.DATA
         ;-------------------------------------------------------------
         ; З А П И С Ь   Л О К А Л Ь Н Ы Х  К О Н С Т А Н Т  DUMP87
         ;
         ;       ------- этот раздел только считывается -------
         ;
         ; "_lab" - метка раздела
         ; "_cnd" - условие для метки
         ; "_siz" - число байтов в условии
         ;

                                      - 10-31 -
         @CRet   MACRO            ;; новое макроопределение строки
                 db       0Dh,0Ah
                 ENDM
         ;
         LINE1   EQU      $
                 @CRet
                 db '=====================NPX DUMP ====================
                 db '==='
                 @CRet
                 db         'Infinity:  $'
         rnd_lab db         '    Round:........ $'    ; метка
         pre_lab db         '    Precision:  $'       ; метка
         inf_siz db         7
         inf_cnd db         'Proj. $'     ; состояние бесконечности
                 db         'Affine$'     ; состояние бесконечности
         rnd_siz db         5
         rnd_cnd db         'near$'       ; состояние округления
                 db         'down$'       ; состояние округления
                 db         'up  $'       ; состояние округления
                 db         'chop$'       ; состояние округления
         pre_siz db         3
         pre_cnd db         '24$'         ; состояние точности "ret"
                 db         '**$'         ; состояние точности "ret"
                 db         '53$'         ; состояние точности "ret"
                 db         '64$'         ; состояние точности "ret"
         ;
         LINE2   EQU        $
                 @CRet
                 db         'Inst Addr: $'      ; "x:xxxx"
         opadr   db         '    Oper Addr: $'  ; "x:xxxx"
         ocode   db         '    Opcode:  D$'   ; "xxx","ret","ret"
         ;
         LINE3   EQU        $
                 @CRet
                 @CRet
                 db         '    INT PRE UND OVR ZER DEN IOP'
                 db         '       C3 C2 C1 C0'
                 db         'Masked:$'
         ;                  коды условия          "ret"
         LINE6   EQU        $
                 @CRet
                 db         'Signal:$'          ; "ret"
         marky   db         ' x  $'
         markn   db         '    $'
         ;
         LINE8   EQU        $
                 @CRet
                 db         'ST($'              ; "x"
         paren   db         ')   $'
         plus    db         '+ $'
         minus   db         '- $'               ; "xxxx"
         space10 db         '          '        ; 10 пробелов
         SPACE2  EQU        $ + 1               ; 2 пробела
         SPACE1  EQU        $ + 2               ; 1 пробел
         space3  db         '   $'              ; 3 пробела
                                                ; "xxxx" 4 раза
         truenum db         ' #$'               ; " #x", чем флаг " "

                                      - 10-32 -
         tag_siz db         6
         tag_cnd db         'Valid$'            ; состояние признака
                 db         'Zero $'            ; состояние признака
                 db         'Spec.$'            ; состояние признака
                 db         'Empty$'            ; состояние признака
         ;
         LINE9   EQU        $
                 @CRet
                 db         '----------------------------------------'
                 db         '--------'
         CRLF    EQU        $
                 @CRet
                 db         '$'
         ;
         #.CODE
         ;
         dump87  ENDP
         ;=============================================================
                 END             ; конец программ(ы)
         ----------------------------------------------------------------
             Программа DUMP87  получает  отображаемую информацию с помощью
         команды NPX FSAVE.  Эта команда сохраняет состояние NPX в 94 бай-
         тах в формате,  показанном на рисунке 10-12.  Тем не менее, FSAVE
         инициализирует NPX таким же образом,  как если бы была  выполнена
         команда FINIT. Это позволяет числовой подпрограмме сохранить  со-
         стояние NPX и затем инициализировать его одной командой,  которая
         аналогична  помещению  в  стек  регистров  и их очистке для ввода
         подпрограммы главного центрального процессора.  Так как мы  хотим
         продолжить  обработку без прерывания,  необходимо дополнить FSAVE
         командой FRSTOR,  которая заново загружает NPX,  исходя из сохра-
         ненной информации.
             Из рисунка 10-12 видно,  что первые 14 байтов сохраненной ин-
         формации идентичны байтам, сохраненным командой FSTENV (сохранить
         среду). Команда FSTENV не реинициализирует NPX; скорее, она пред-
         назначена  для  предоставления программисту доступа к информации,
         необходимой для обработки особых  ситуаций:  слова  состояния,  а
         также указателей команды и операнда. Команды FSAVE и FSTENV имеют
         совокупную команду FLDENV, которая может перезагрузить среду, ис-
         ходя из сохраненной информации.

                          Использование программы DUMP87

             Завершающая часть программы ничего не делает с NPX.  Она при-
         меняет структуру и определения записи MASM для разбиения информа-
         ции, полученной с помощью команды FSAVE, и представления ее поль-
         зователю.  Формат,  использованный  для представления информации,
         описан в разделе заголовка программы.  Программа,  приведенная  в
         листинге,  пригодна  для  трансляции  и  включения в библиотечный
         файл. Если Вы выполните указанную процедуру, программа DUMP87 мо-
         жет  быть  включена  в любой другой файл путем сопоставления имен
         сегмента и класса DUMP87,  посредством описания ее как внешней  и
         обеспечения внешней программы BIN2HEX. Один из способов использо-
         вания DUMP87 выглядит следующим образом:
         code    segment para public 'code'  ; библиотечный сегмент
                 assume  cs:code,ds:code,es:code,ss:code
                 extrn   dump87:near         ; БИБЛИОТЕЧНАЯ ПРОГРАММА
                 ORG     0100h               ; ФОРМАТ .COM
         main    proc    far
         start:

                                      - 10-33 -
                              FSAVE
                            (94 байта)
         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
        Ъ Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д Д ДЪДДДДДДДДДД¬
        ¦¦                                                   ¦   Слово  ¦
        ¦                                                    ¦управления¦
        ¦¦                                                   ГДДДДДДДДДДґ
        ¦                                                    ¦   Слово  ¦
        ¦¦                                                   ¦ состояния¦
  FSTENV¦                                                    ГДДДДДДДДДДґ
   (14  ¦¦                                                   ¦   Слово  ¦
   байт)¦                                                    ¦ признака ¦
        ¦¦                                     ЪДДДДДДДДДДВДДБДДДДДДДДДДґ
        ¦                                      ¦   Код    ¦  Указатель  ¦
        ¦¦                                     ¦ операции ¦   команды   ¦
        ¦                                      ГДДДДДДДДДД†ДДДДДДДДДДДДДґ
        ¦¦ЪДД Знаковый бит                     ¦          ¦  Указатель  ¦
        ¦ ¦                                    ¦          ¦   операнда  ¦
        АЪДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДБДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(0)¦ ¦Экспонента¦               Мантисса                          ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(1)¦ ¦          ¦                                                 ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(2)¦ ¦          ¦                                                 ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(3)¦ ¦          ¦                                                 ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(4)¦ ¦          ¦                                                 ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(5)¦ ¦          ¦                                                 ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(6)¦ ¦          ¦                                                 ¦
         ГД†ДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦ ¦          ¦                                                 ¦
    ST(7)¦ ¦          ¦                                                 ¦
         АДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
         ¦ ¦                      Смещение байта
             ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         ¦ / +9  / +8  / +7  / +6  / +5  / +4  / +3  / +2  / +1  / +0  /
          ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

              Рис.10-12. Структура памяти FSAVE и FSTENV.


                                      - 10-34 -
                 FINIT                       ; инициализировать NPX
                  .
                  .
                  .
                 call    dump87              ; анализировать NPX
                  .
                  .
                  .

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


                       Использование NPX для преобразований
                      двоичного кода в десятичный в двоичный

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

                                 Операции с целым

             Выполнить преобразование целого в двоичный код на NPX просто,
         благодаря наличию команд загрузки  и  хранения  упакованного  BCD
         FBLD и FBSTR. Все что нужно, это простая программа главного цент-
         рального процессора для упаковки цифр BCD из  строк  ASCII  и  их
         распаковки. Для перехода из десятичного кода в двоичный, загрузи-
         те десятичное число с помощью FBLD и затем выполнить команду  за-
         писи десятичного, FBSTP.
             Обратите внимание,  что если преобразованные числа  малы  для
         заполнения  16-битого регистра (или 32-битового регистра в 8038),
         необязательно использовать NPX для перевода из десятичного форма-
         та в двоичный.  Это связано с тем, что упаковка цифр и выполнение
         последовательности FBLD-FIST требует больше времени,  чем следую-
         щая  стандартная  программа преобразования с использованием "мно-
         гократного сдвига":

         ; Предполагаемое число аккумулировано в AX, а новая цифра
         ; находится в регистре CL.
             shr   ax,1         ; имеющееся число x 2
             mov   bx,ax        ; сохранить
             shr   ax,1         ; число х 4
             sch   ax,1         ; число х 8
             add   ax,bx        ; (# x 8) + (# x 2) = # x 10
             xor   ch,ch        ; подготовить к 16-битовому добавлению
             add   ax,cx        ; добавлена следующая цифра

             Преобразование в NPX маленьких чисел (от одной до трех  деся-
         тичных цифр) из десятичного кода в двоичный, включая время, необ-
         ходимое для создания вектора упакованного BCD  из  строки  ASCII,
         продолжительнее в два раза.
             Если числа больше 16 двоичных битов, главный центральный про-
         цессор  начинает работать медленнее,  так как он должен постоянно
         выполнять проверку на наличие переноса,  возможных переполнений и

                                      - 10-35 -
         потерь значимости,  и так далее. В диапазоне от 16 до 64 двоичных
         битов,  NPX выполняет операции преобразования чрезвычайно быстро!
             Так как  длина  числа  не  превышает  18 десятичных цифр (что
         трудно достичь!),  то не требуются операции NPX,  связанные с ко-
         мандами  загрузки и сохранения.  Как только длина чисел превышает
         18 цифр,  они должны быть масштабированы,  и мы входим в  область
         вещественных чисел с плавающей запятой.


                           Операции с плавающей запятой

             Обработка преобразований между десятичными и двоичными числа-
         ми в мире плавающей запятой в основном является делом масштабиро-
         вания.  Конечно,  мы можем использовать команды FBLD и FBSTP  для
         ввода и вывода из NPX базисных чисел, но затем нам необходимо до-
         полнить эти числа числом десять в некоторой  степени.  Для  того,
         чтобы понять,  как выполняются эти операции, давайте вспомним не-
         которые основные математические равенства, связанные с преобразо-
         ванием чисел.
                  1. 10(X) = 2(X * log 10 по основанию 2)
                  2. E(X) = 2(X * log E по основанию 2)
                  3. Y(X) = 2(X * log Y по основанию 2)
                  4. lоg X = log 2 * log X по основанию 2
                  5. log X по основанию E = (log 2 по основанию E) *
                     (log X по основанию 2)

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

                  A. F2XM1 вычисляет 2(X) - 1
                  B. FLDL2T константа log 10 по основанию 2
                  C. FLDL2E константа log E по основанию 2
                  D. FYL2X вычисляет Y * log X по основанию 2
                  E. FLDLG2 константа log 2
                  F. FLDLN2 константа log 2 по основанию E

             Мы знаем,  что как только загружается целое число,  мы должны
         или умножить его на степень десяти для числа с положительной экс-
         понентой по основанию десять, или разделить его на степень десяти
         для  числа  с  отрицательной экспонентой по основанию десять.  Из
         правила 1 видно,  что первым шагом к получению степени  числа  10
         является возведение числа 2 в некоторую степень Х.


                                  Вычисление 2

             Обычно, число 2 может быть возведено  в  степень  посредством
         простого  сдвига,  как  раз  то,  что  делает  NPX своей командой
         FSCALE. К сожалению, это не является полным решением, так как це-
         лые степени числа 10 не соответствуют целым степеням числа 2. Не-
         обходимо вычислить некоторую дробную часть  числа  2.  Для  этого
         служит команда NPX F2XM1 (смотрите правило А).
             Команда F2XM1 может возводить 2 в степень Х,  где значение  Х
         лежит  в диапазоне от 0.0 до 0.5 включительно.  Взяв произвольное
         число Х,  мы можем разбить его на целую и дробную части, вычислив
         следующие выражения:

                                      - 10-36 -

                   целая (Х) = FRNDINT(X)
                  дробная(Х) = FSUB X - целая(Х)

             Целая часть Х используется в FSCALE для возведения числа  2 в
         целую степень, а дробная часть применяется командой F2XM1. Мы мо-
         жем использовать две последовательные операции,  так  как  знаем,
         что для любых значений Y и Z справедливо следующее равенство:

                  2(Y+Z)  = 2(Y) * 2(Z)

             Абсолютное значение  дробной  части Х находится в пределах от
         0.0 до 0.5,  с учетом того,  что управление округлением NPX уста-
         новлено в режим округления к ближайшему,  что обеспечивает макси-
         мальную дробную часть, равную 0.5.
             Затем мы можем вычислить весь результат, применив F2XM1, при-
         бавив 1 обратно к результату, и использовав FSCALE. Конечно, если
         дробная часть отрицательная, мы должны удостовериться, что приме-
         няем ее абсолютное значение и для получения правильного результа-
         та использовать равенство

                  2(Y-Z) = 2(Y)/2(Z)

         Это последовательность событий имеет место в программе EXP2,  на-
         ходящаяся ближе к концу следующего листинга (Листинг 10-2).


                                  Вычисление 10

             Теперь, определив,  как вычислять 2 в степени Х, мы выполнили
         главную часть вычисления 10 в степени Х.  Из правила 1 мы  знаем,
         что

                  10(X) = 2(X) * log 10 по основанию 2,

         значит нам нужно найти значение

                  X * log 10 по основанию 2

         в целях использования только что разработанной программы возведе-
         ния числа 2 в степень Х.  Из правила B видно,  что NPX может дать
         нам значение логарифма 10 по основанию 2. Вычисление 10 в степени
         Х выполняется операцией FLDL2T,  с последующим умножением FMUL  и
         завершается вызовом EXP2.  Эти команды имеются в программе EXP10,
         содержащейся в листинге 10-2.
             Изменяя тождества логарифм 10 по основанию 2 на логарифм e по
         основанию 2 и логарифм Х по основанию 2, мы можем вычислить с по-
         мощью программы EXP2 значения 10 в степени Х, e в степени Х и Y в
         степени Х.


                Функция масштабирования десятичного в вещественное

             Что нам делать с полученным значением 10 в степени Х? Нам не-
         обходимо это число для использования  экспоненциального  формата.
         Имея  число в формате упакованного BCD и целое слово Х для экспо-
         ненты, мы можем преобразовать части в вещественное число с плава-

                                      - 10-37 -
         ющей запятой посредством загрузки значащей части упакованного BCD
         с помощью FBLD.  Мы вычисляем 10 в степени абсолютного значения Х
         и  затем либо умножаем результат на мантиссу для положительного Х
         (FMUL),  либо делим мантиссу на результат от возведения 10 в сте-
         пень Х для отрицательных экспонент (FDIV). Это, как Вы, возможно,
         уже поняли,  делает программа DEC2FLT,  представленная в листинге
         10-2.  Программа выглядит длиннее, так как нам необходимо следить
         и устанавливать знак экспоненты.
             Используя пакет программ EXP2,  EXP10 и DEC2FLT,  можно полу-
         чить число, состоящее из двух частей (мантисса в виде упакованно-
         го BCD и целая экспонента), которое главный центральный процессор
         генерирует и превращает в вещественное число с  плавающей запятой
         внутри NPX.


                Функция масштабирования вещественного в десятичное

             Введя числа в NPX,  мы можем производить вычисления над ними.
         Если мы выходим из комнаты,  то можем сохранить их в общей памяти
         как временные вещественные (это делает команда FSTP).  Но что де-
         лать,  когда  надо взглянуть на результаты?  Что надо сделать для
         того,  чтобы преобразовать вещественное число с плавающей запятой
         в целое число, состоящее из двух частей?
             Ответ заключается в том,  что мы должны поработать со смещен-
         ной  экспонентой  NPX  таким образом,  чтобы NPX выдало нам целую
         мантиссу. При сохранении числа как строки упакованного BCD коман-
         да FBSTP сначала округляет число к ближайшему целому.  Если число
         слишком велико для представления строкой упакованного BCD, NPX не
         может сохранить это число.  Если число слишком мало,  то точность
         мантиссы при округлении числа теряется. Для того, чтобы применить
         команду FBSTP, мы должны сначала убедиться в том, что число, хра-
         нящееся в регистре, лежит в соответствующем диапазоне.
             Мы можем сказать,  что число лежит в соответствующем диапазо-
         не,  так как его смещенная экспонента (двоичная десятичная  запя-
         тая)  имеет  значение  меньше,  чем  64 (в противном случае число
         слишком велико) и больше,  чем количество двоичных цифр  мантиссы
         (в противном случае мы теряем точность).  Обычно мы выбираем чис-
         ло,  которое нам даст хорошую точность. Для числа, которое мы хо-
         тим  иметь с точность до 10 десятичных цифр,  значение экспоненты
         32 является хорошим значением.  Это означает,  что двоичная деся-
         тичная запятая находится в бите 32,  примерно на половине пути до
         конца плавающей запятой.  Не слишком большое и не слишком малень-
         кое.
             Как быть, если число имеет экспоненту, не лежащую в этом диа-
         пазоне?  Мы должны поменять экспоненту. Первым шагом является оп-
         ределение того,  какую экспоненту надо реально  использовать.  Мы
         применяем  команду FXTRACT,  которая разбивает регистр данных NPX
         на два,  один из которых содержит мантиссу с нулевой  экспонентой
         (ST),  а  другой  содержит  правильную экспоненту первоначального
         числа как вещественное число (ST(1)).  Мы интересуемся  регистром
         ST(1).
             Первым шагом вычисления является  определение  того,  сколько
         двоичных десятичных мест мы убрали. Иными словами, мы хотим опре-
         делить расстояние между желаемой и существующей экспонентой.  Ко-
         манда FSUB может сказать нам это очень быстро.
             Зная расстояние,  можем ли мы использовать его в качестве ко-
         эффициента  масштабирования (с помощью FSCALE) для первоначальной

                                      - 10-38 -
         экспоненты?  Нет,  потому что при отображении числа в экспоненци-
         альном формате нам надо показать пользователю экспоненту в виде

                  + 1.2345600000Е + 00,

         а мы не можем это сделать, если экспонента является степенью чис-
         ла 2.  Идея этого упражнения заключается в том,  чтобы NPX выдало
         целое число и затем узнать, на сколько степеней числа 10 это чис-
         ло было сдвинуто для получения целого.  Привести в порядок экспо-
         ненциальный формат.
             Нам нужно каким-то образом преобразовать  расстояние, которое
         в  настоящее  время выражает степень числа 2,  в расстояние целых
         степеней числа 10.  Оказывается, взаимоотношение между двумя зна-
         чениями выражается следующим правилом:
                  2(X) = 10(X * log 2)
                  2(X) = 10(X * log 10 по основанию 2)
             Второе взаимоотношение является результатом тождества:

                  log b по основанию a = 1/log a по основанию b

             Не смотря на то,  какой способ мы использовали, мы определили
         значение Х (для выражения 10 в степени Х), требуемое для создания
         соответствующего коэффициента масштабирования.  Мы можем  создать
         коэффициент, применяя команды FLDLG2 (логарифм числа 2 по основа-
         нию 10) и FMUL,  или команды FLDL2T (логарифм числа 10 по основа-
         нию 2) FDIV.  Тем не менее, эти методы дают точное значение Х для
         числа 10 в степени Х и нам необходимо ближайшее целое. Поэтому мы
         применим команду FRNDINT для округления числа и получим нашу экс-
         поненту по основанию 10.
             Имея экспоненту, мы возводим 10 в степень Х (с помощью коман-
         ды EXP10) и получаем коэффициент масштабирования для перевода ве-
         щественного числа в целое (с помощью FMUL). Экспонента 10 возвра-
         щается командой FIST (сохранить целое) и мантиссу  командой FBSTP
         (сохранить упакованный BCD).  Все, за исключением сохранения BCD,
         содержится в программе FLT2DEC,  представленной в листинге 10-2.
             Другой полезной хитростью является то,  что однажды записав в
         память  число  в формате упакованного BCD,  мы можем использовать
         программу отображения двоичного кода в шестнадцатиричном  (напри-
         мер,  BIN2HEX) для вывода на экран цифр, потому что они очень по-
         хожи на шестнадцатиричное число.
             Мы говорили о листинге 10-2, и вот, наконец, подошла его оче-
         редь.  Обратите внимание, что как и в программе DUMP87, она также
         сформатирована для использования в качестве библиотеки. Кроме то-
         го,  все операции выполняются в стеке главного центрального  про-
         цессора или в ячейках, определенных вызывающим оператором, поэто-
         му проблем с переносимостью не возникнет.

             Листинг 10-2. DE2FLT, FLT2DEC и программы экспоненты
                           EXP2, EXP10, EXPE и EXRY
         ______________________________________________________________

         PAGE     60,132       ; широкий листинг
         #.8087                ; разрешить трансляцию команд 8087 NPX
         ;
                  PUBLIC  dec2flt  ; определить библиотечную программу
                  PUBLIC  flt2dec  ; определить библиотечную программу

                                      - 10-39 -
                  PUBLIC  exp10    ; определить библиотечную программу
                  PUBLIC  expE     ; определить библиотечную программу
                  PUBLIC  expY     ; определить библиотечную программу
                  PUBLIC  exp2     ; определить библиотечную программу
         ;
         ;=============================================================
         ;                   Р Е А Л И З А Ц И Я
         ;
         #.MODEL   SMALL
         ;
         #.CODE
         ;
         ;*************************************************************
         ; DEC2FLT - Преобразует целое десятичное с экспонентой в
         ;      вещественное число с плавающей запятой. Записывает
         ;      экспоненту и указатель в строку упакованного BCD в
         ;      стеке. Возвращает результат в ST(0).
         ;
         ; Использовать: push   offset (tbyte ptr packed_BCD)
         ;               push   exponent
         ;               call   dec2flt
         ;
         ; Требования:   3 ячейки стека
         ; Обозначения:  N ..... экспонента для 10**N
         ;               S ..... мантисса вещественного числа
         ;-------------------------------------------------------------
         ;
         #.DATA
         D2FLTD   STRUCT
         d2fltbp  dw     ?           ; прежний базовый указатель
                  dw     ?           ; возвратить адрес
         d2fltex  dw     ?           ; экспонента
         d2fltpd  dw     ?           ; указатель для упакованного BCD
         D2FLTD   ENDS
         ;
         #.CODE
         dec2flt  PROC   NEAR
                  push   bp
                  mov    bp,sp                   ; параметры адреса
                  cmp    word ptr [bp].d2fltex,0 ; проверить знак
                                                 ; ... экспоненты
                  jz     d2flt_npx               ; если ноль, то 10**N
                                                 ; ... не нужно
                  pushf                    ; сохранить знак экспоненты
                  jg     d2flt_pos         ; если положительное,
                                           ; ... начать 10**N
                  neg    word ptr [bp].d2fltex   ; в противном случае
                                ; ... сделать экспоненту положительной
         d2flt_pos:
                  FILD   word ptr [bp].d2fltex ; получить экспоненту 10
                  call   exp10                 ; вычислить 10**N
         d2flt_npx:                     ; войти сюда, если экспонента 0
                  push   si
                  mov    si,[bp].d2fltpd       ; получить указатель
                                               ; ... упакованного BCD
                  FBLD   tbyte ptr [si]        ; ST => S; ST(1) = 10**N
                  pop    si

                                      - 10-40 -
                  popf                   ; восстановить знак экспоненты
                  jz     d2flt_end       ; выполнено, если экспонента 0
                  jl     d2flt_neg      ; если отрицательная, разделить
                  FMUL                  ; ST => мантисса * 10**N
                  jmp    d2flt_end      ; и выполнено
         d2flt_neg:
                  FDIVR                 ; ST => мантисса / 10**N
         d2flt_end:
                  pop    bp             ; восстановить bp
                  ret    4
         dec2flt  ENDP
         ;
         ;*************************************************************
         ; FLT2DEC - Преобразует вещественное число с плавающей запятой
         ;       в целое с экспонентой. ST(0) содержит преобразуемое
         :       число. Стек содержит количество требуемых двоичных
         ;       цифр и указатель расположения экспоненты 10.
         ;       Результат возвращается ST(0), преобразованный в целое,
         ;       и пишет экспоненту в указанном месте.
         ;
         ; Использовать: push  sig_digits
         ;               push  offset (word ptr to exponent)
         ;               call  flt2dec
         ;
         ; Требования:   4 ячейки стека
         ; Обозначения:  R ..... Отображаемое вещественное число
         ;               N ..... Экспонента 10 для перевода R в целое
         ;               I ..... Целая часть результата
         ;               n(N) .. Ближайшее к N целое
         ;-------------------------------------------------------------
         ;
         #.DATA
         F2DECD  STRUC
         f2deccw dw     ?           ; оригинальное слово управления
         f2decbp dw     ?           ; прежний базовый указатель
                 dw     ?           ; адрес возврата
         f2decex dw     ?           ; указатель экспоненты
         f2decsd dw     ?           ; количество значащих двоичных цифр
         F2DECD  ENDS
         ;
         #.CODE
         ;* проверить управление округлением сейчас - применить другой?
         F2DECCT EQU    03BFh      ; новое слово управления - округлить
                                   ; ... ближайшее
         ;
         flt2dec PROC   NEAR
         ;
         ; Установить слово управления NPX и открыть запись в стек:
                 push   bp        ; сохранить прежний базовый указатель
         STKADJ1 EQU    f2decbp-F2DECD
                 sub    sp,STKADJ1  ; сделать запись в стеке
                 mov    bp,sp       ; адрес новой структуры
                 push   ax          ; сохранить AX
                 mov    ax,F2DECCT  ; поместить новое слово управления
                                    ; ... в стек
                 push   ax
                 FSTCW  word ptr [bp].f2deccw

                                      - 10-41 -
                 FLDCW  word ptr [bp-4]      ; установить округление к
                                             ; ... ближайшему целому
                 pop    ax                   ; очистить стек
                 pop    ax                   ; восстановить AX
         ;
         ; Найти N для 10**N в целях преобразования в целое:
                 FLD    ST(0)  ; продублировать R (сохранить до конца)
                 FXTRACT       ; ST(1) => экспоненциальная часть R
                 FSTP   ST(0)  ; ST => экспоненциальная часть R
                 FISUBR word ptr [bp].f2decsd   ; значащие ...
                            ; ... цифры - экспонента = # цифр масштаба
                 FLDL2T     ; ST => log2 (10), ST(1) => масштаб
                 FDIV       ; ST => масштаб / log2 (10) = N
                 FRNDINT    ; ST => n(N)
         ;
         ; Сохранить nint(N) как экспоненту и вычислить 10**nint(N)
                 push   si
                 mov    si,[bp],f2decex ; получить указатель экспоненты
                 FIST   word ptr [si]   ; сохранить масштаб основания 10
                 FWAIT
                 neg    word ptr [si]   ; указание двигать десятичную
                                        ; ... запятую
                 pop    si
                 call   exp10           ; вычислить 10**N (масштаб)
         ;
         ; В ST(1) теперь находится R (первоначальное вещественное
         ; число) - масштабировать его:
                 FMUL                   ; ST => R * 10**N = целое
                 FLDCW  word ptr [bp].f2deccw ; восстановить слово
                                              ; ... управления
         STKADJ2 EQU    f2decbp-F2DECD
                 add    sp,STKADJ2   ; восстановить первоначальный стек
                 pop    bp           ; восстановить базовый указатель
                 ret    4            ; очистить стек для возврата
         flt2dec ENDP
         ;
         ;*************************************************************
         ; EXP10 - Возводит число 10 в степень ST(0).
         ;       Возвращает результат в ST(0).
         ;
         ; Использует формулу: 10**N = 2**(N*log2(10))
         ;
         ; ВЫЗЫВАЕТ:      EXP2
         ;
         ; Требования:    3 ячейки стека
         ; Обозначения:   N ...... экспонента для 10**N
         ;                X ...... эквивалентная экспонента для 2**X
         ;                n(x) ... ближайшее к Х целое
         ;                f(x) ... дробная часть Х
         ;-------------------------------------------------------------
         exp10  PROC    NEAR
                FLDL2T                     ; ST > log2 (10); ST(1) => N
                FMUL                       ; ST => N * log2 (10) => X
                call    exp2               ; возвести 2 в степень ST
                ret                        ; ... для 10**N
         exp10  ENDP
         ;

                                      - 10-42 -
         ;*************************************************************
         ; EXPE - Возводит число E в степень ST(0).
         ;       Возвращает результат в ST(0).
         ;
         ; Использует формулу: E**N = 2**(N*log2(E))
         ;
         ; ВЫЗЫВАЕТ:      EXP2
         ;
         ; Требования:    3 ячейки стека
         ; Обозначения:   N ...... экспонента для E**N
         ;                X ...... эквивалентная экспонента для 2**X
         ;                n(x) ... ближайшее к Х целое
         ;                f(x) ... дробная часть Х
         ;-------------------------------------------------------------
         expE   PROC    NEAR
                FLDL2E                     ; ST > log2 (e); ST(1) => N
                FMUL                       ; ST => N * log2 (e) => X
                call    exp2               ; возвести 2 в степень ST
                ret                        ; ... для E ** N
         expE   ENDP
         ;
         ;*************************************************************
         ; EXPY - Возводит Y [ST(0)] в степень N [ST(1)]
         ;      Возвращает результат в ST(0)
         ;      ST(1) (значение N) теряется!
         ;
         ; Использует формулу: Y**N = 2**(N*log2(Y))
         ;
         ; **** ПРИМЕЧАНИЕ: Y ДОЛЖНО БЫТЬ ПОЛОЖИТЕЛЬНОЕ ****
         ;
         ; ВЫЗЫВАЕТ:      EXP2
         ;
         ; Требования:    3 ячейки стека
         ; Обозначения:   N ...... экспонента для Y**N
         ;                X ...... эквивалентная экспонента для 2**Y
         ;                n(x) ... ближайшее к Х целое
         ;                f(x) ... дробная часть Х
         ;-------------------------------------------------------------
         expY   PROC    NEAR
                FYL2X                    ; ST => N * log2 (Y); (Y) => X
                call    exp2               ; возвести 2 в степень ST
                ret                        ; ... для Y ** N
         expY   ENDP
         ;
         ;*************************************************************
         ; EXP2 - Возводит число 2 в степень ST(0).
         ;       Возвращает результат в ST(0).
         ;
         ; Требования:    3 ячейки стека
         ; Обозначения:   X ...... экспонента для 2**X
         ;                n(x) ... ближайшее к Х целое
         ;                f(x) ... дробная часть Х
         ;-------------------------------------------------------------
         ;
         #.DATA
         EXP2D   STRIC
         exp2cc  dw     ?              ; коды условий

                                      - 10-43 -
         exp2cw  dw     ?              ; оригинальное слово управления
         exp2bp  dw     ?              ; прежний базовый указатель
                 dw     ?              ; адрес возврата
         EXP2D   ENDS
         ;
         #.CODE
         EXP2CT  EQU    03BFh    ; новое слово управления - округлять к
                                 ; ... ближайшему
         exp2    PROC   NEAR
         ;
         ; Установить слово управления NPX и открыть запись в стек:
                 push   bp        ; сохранить прежний базовый указатель
         STKADJ3 EQU    exp2bp-EXP2D
                 sub    sp,STKADJ3  ; сделать запись в стеке
                 mov    bp,sp       ; адрес новой структуры
                 push   ax          ; сохранить AX
                 mov    ax,EXP2CT   ; поместить новое слово управления
                                    ; ... в стек
                 push   ax
                 FSTCW  word ptr [bp].exp2cw
                 FLDCW  word ptr [bp-4]      ; установить округление к
                                             ; ... ближайшему целому
                 pop    ax                   ; очистить стек
                 pop    ax                   ; восстановить AX
         ;
         ; Начать обработку числа:
                 FLD    ST(0)      ; ST => ST(1) => X для 2**X
                 FRNDINT           ; ST => n(N); ST(1) => X
                 FXCH              ; ST => X; ST(1) => n(N)
                 FSUB   ST,ST(1)   ; ST => f(X); ST(1) = n(X)
                 FTST              ; установить коды условий
                 FSTSW  word ptr [bp].exp2cc  ; сохранить коды условий
                 FWAIT
                 and    byte ptr [bp+1].exp2cc,45h ; замаскировать все
                                             ; ... кроме кодов условий
                 cmp    byte ptr [bp+1].exp2cc,1 ; проверить на
                                                 ; ... отрицательность
                 ja     exp2_err     ; NAN или бесконечность -> ошибка
                 je     exp2_neg     ; дробная часть минусовая
         ;
                 F2XM1             ; ST => (2**f(X)) - 1; ST(1) = n(X)
                 FLD1              ; ST => 1; ST(1) => (2**f(X))-1;
                                   ; ... ST(2) = n(X)
                 FADD              ; ST => 2**f(X); ST(1) => n(X)
                 FSCALE       ; ST => 2**(X) => 2**(N(log2(?)) => ?**N
                 FSTP         ; ST => ?**N; ST(1) => восстановлен
                 jmp    exp2_mer   ; соединить
         ;
         exp2_neg:
                 FABS         ; ST => 1-f(x); ST(1) = n(X) + 1
                 F2XM1        ; ST => (2**(1-f(x)))-1; ST(1) = n(X) + 1
                 FLD1         ; ST => 1; ST(1) => (2**(1-f(x)))-1
                 FADD         ; ST => 2**(1-f(x)); ST(1) => n(X) +1
                 FXCH         ; ST => n(X) + 1; ST(1) => 2**(1-f(x))
                 FLD1         ; ST => 1; ST(1) = n(X) + 1
                 FSCALE       ; ST => 2**(n(X) + 1);
                              ; ... ST(2) => 2**(1-f(x))

                                      - 10-44 -
                 FDIRP  ST(2),ST ; ST(1) => 2**(n(X) + 1)/2**(1 - f(x))
                 FSTP   ST(0) ; ST => 2**(n(x) + 1 - 1 + f(x) => 2**(X)
         ;
         exp2_mer:
                 clc                            ; нет ошибок
         exp2_out:
                 FLDCW  word ptr [bp].exp2cw    ; восстановить слово
                                                ; ...состояния
         STKADJ4 EQU    exp2bp-EXP2D
                 add    sp,STKADJ4   ; восстановить первоначальный стек
                 pop    bp           ; восстановить базовый указатель
                 ret
         exp2_err:
                 stc                 ; были ошибки
                 jmp    exp2_out
         exp2    ENDP
         ;*************************************************************
                 END                 ; конец программ
         ______________________________________________________________


                                    Заключение

            Приводя примеры программ отладки и ввода/вывода,  мы надеемся,
         что объяснили Вам,  как работает NPX и какие возможности он  пре-
         доставляет,  а  также  вдохновили  Вас  на разработку собственных
         программ для NPX.  Имея такое средство, Вы смогли бы заняться лю-
         бой интересующей Вас работой. Тригонометрический анализ, преобра-
         зования Фурье - все намного проще,  если Вы используете  вычисли-
         тельную мощь NPX. Удачи Вам и успешной числовой обработки!




                            ЧАСТЬ III.   ВОССТAHОВЛЕНИЕ


                 Глава 11. СТРУКТУРА ДИСКА И ВОССТAHОВЛЕНИЕ ФАЙЛОВ


                 Основные принципы восстановления файлов
                 Обзор процедур восстановления
                 Восстановление разрушенных файлов при помощи утилит
                 CHKDSK и RECOVER
                 Восстановление стертых файлов
                 Заключение


              Если вы уже некоторое время поработали с операционной систе-
         мой  MS-DOS,  вам,  вероятно,  доводилось случайно стирать или по
         ошибке терять файл, который мог бы в дальнейшем вам понадобиться.
         Команда  ERASE  (или  DEL) в операционной системе MS-DOS является
         часто используемой и мощной, а по своей природе - это разрушающая
         команда. Разрушительная сила, конечно, требуется этой команде для
         выполнения ею своих функций, но если вы проявите беспечность, эта
         команда станет губительной, чего вы вряд ли желаете.
              Единственная предусмотренная защита от  случайного  удаления
         файлов  командами ERASE или DEL появляется на экране тогда, когда
         вы собираетесь удалить все файлы на диске  и  указываете  команду
         стирания в виде "erase *.*". Только в этом случае на экране появ-
         ляется вопрос о том,  действительно ли вы хотите выполнить  такую
         операцию. Если в качестве ответа вы введете символ "n" (нет), вы-
         полнение команды будет прекращено. В тех же случаях, когда вы ис-
         пользуете  указанные команды удаления для стирания отдельных фай-
         лов  или  групп  файлов,  единой  защитой  вам  может   послужить
         самоконтроль:  сделайте паузу прежде, чем нажать клавишу "Return"
         (или "Enter"), и внимательно еще раз проверьте набранную вами ко-
         манду для удаления файлов. Даже тогда (и это не зависит от степе-
         ни вашей уверенности в правильности указания  файла  или  файлов,
         подлежащих  удалению)  среди удаленных вами файлов могут быть та-
         кие,  которые удалять не следовало. Что ж, все мы люди и когда-то
         совершаем ошибки! Поскольку компьютеры устроены так, что мгновен-
         но подчиняются вашим командам,  запрос на удаление  файлов  будет
         выполнен  немедленно  после  нажатия  вами  клавиши "Return" (или
         "Enter") в конце командной последовательности.
              Файл может  быть  также стерт выполняющейся в текущий момент
         программой.  Текстовые редакторы и другие программы, обеспечиваю-
         щие средства управления файлами, могут содержать команды (в явной
         или неявной форме) по удалению файлов.  Кроме того,  файлы  могут
         оказаться стертыми в результате сбоев оборудования, сбоев в пода-
         че питания или в результате каких-то запутанных переходов в самой
         программе.
              Итак, что нужно делать,  если непродублированный нигде  файл
         был  по  ошибке удален?  К счастью,  файловая система MS-DOS была
         разработана так,  что в некоторых  случаях  восстановить  стертый
         файл  не составляет труда:  существует несколько специальных ути-
         лит, предназначенных для восстановления стертых файлов. Некоторые
         из  этих  утилит  поставляются  в  основном комплекте программных
         средств. Другие можно приобрести отдельно. Из отдельно приобрета-

                                      - 11-2 -
         емых  средств  наиболее распространенными являются "Norton-утили-
         ты" и "Mace-утилиты".  Аналогичный указанным программный  продукт
         под названием "Ultra-утилиты" состоит из набора утилит, имеющего-
         ся в "свободной торговле"*.  В настоящее время его можно  достать
         через  многочисленные  общедоступные каналы распространения прог-
         раммного обеспечения.

             В настоящей главе мы обсудим процедуры восстановления стертых
         файлов  и использование утилит CHKDSK и RECOVER операционной сис-
         темы MS-DOS для восстановления разрушенных или потерянных файлов.
         Мы  будем также обсуждать применение других способов восстановле-
         ния файлов, включающих в себя коммерческие программы "Norton-ути-
         лит" и поддерживаемые пользователем программы "Ultra-утилит".
              Для того,  чтобы понять работу указанных  выше  средств,  вы
         должны  понимать  работу  всей файловой системы MS-DOS,  тогда вы
         сможете понять и ограничения,  которые имеют утилиты восстановле-
         ния файлов. Отметим, что и "Norton-утилиты" и "Ultra-утилиты" ра-
         ботают только в персональных компьютерах фирмы "IBM" или  совмес-
         тимых с ними персональных компьютерах.  "Norton-утилиты", начиная
         с версии 2.01, осуществляют также восстановление файлов, располо-
         женных на  жестких  дисках (имеются в виду 10-Мегабайтные жесткие
         диски в персональных компьютерах типа IBM-PC или в  совместимых с
         ними  средах),  а "Norton-утилиты",  начиная с версии 3.0,  имеют
         поддержку как 20-Мегабайтных жестких дисков модели IBM PC/AT, так
         и  гибких дисков большой емкости (работающих под управлением опе-
         рационной системы MS-DOS версии 3.0 или  более  поздних).  Версия
         4.0  "Norton-утилит"  включает  в себя возможность работы с любым
         носителем дискового типа, работающим под управлением операционной
         системы MS-DOS при условии,  что дисковый носитель отформатирован
         согласно стандартным соглашениям по операционной  системе MS-DOS.
              Несмотря на  то,  что  пакеты  утилит  восстановления файлов
         чрезвычайно полезны при работах в средах,  для которых  они  были
         разработаны,  может так случиться, что они не будут работать над-
         лежащим образом в среде конкретной операционной системе MS-DOS, и
         особенно  тогда,  когда  вашей  системой не является персональный
         компьютер IBM PC или в достаточной  степени  совместимый  с  ними
         персональный  компьютер.  По этой причине в данную главу включено
         описание программы, которую вы можете попробовать запустить в том
         случае,  когда другие утилиты не работают или,  когда вы решаете,
         что они не подходят для вашей машины.  Программа RESCUE проста по
         конструкции  и  может  быть  расширена  и приспособлена под новые
         свойства.  Программа RESCUE предназначена для  работы  с  дисками
         (сменными и постоянными) при условии, что формат дисков соответс-
         твует стандартным соглашениям  по  операционной  системе  MS-DOS.
         Прежде  чем  мы  продемонстрируем вам использование "Norton -ути-
         лит", "Ultra-утилит" и альтернативной им программы RESCUE, давай-
         те  изучим основные принципы системы дисковой памяти MS-DOS и ос-
         новные принципы восстановления файлов.
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДД
             * Способ коммерческого распространения программного  обеспече-
         ния,  при  котором любой пользователь может свободно скопировать и
         использовать программу;  если пользователь  находит  использование
         программы полезным,  он может послать разработчику указанную в до-
         кументации сумму,  после  чего  он  считается  "зарегистрированным
         пользователем"  и  имеет  право  на  получение  информации о новых
         версиях. (Примечание переводчика.)


                                     - 11-3 -

                      Основные принципы восстановления файлов

              Вероятно вам будет небезинтересно узнать,  как можно восста-
         новить стертый файл.  Интуитивно кажется, что если уж файл стерт,
         он должен быть совершенно удален с  поверхности  диска  навсегда.
         Это предположение частично верно,  поскольку после того, как файл
         был стерт он больше не виден и к нему нет доступа при помощи  ни-
         каких стандартных команд MS-DOS.
              Каждый файл,  сохраняемый операционной  системой  MS-DOS  на
         диске, состоит из следующих трех частей:
              - элемента в каталоге, описывающего файл;
              - определенного места для файла;
              - секторов данных,  содержащих сами данные файла.
              При стирании файла затрагиваются только первые  два элемента
         файла:  относящийся к нему элемент в каталоге и пространство,  им
         занятое.  Эти две части выступают в роли  контрольных  точек  для
         операционной  системы MS-DOS для обращения к секторам данных фай-
         ла.  Сектора данных файла,  однако,  не стираются, что и является
         причиной возможности восстановления файла в том случае,  когда вы
         знаете хоть что-то о первых двух частях файла.  Подробнее о расп-
         ределении  памяти  для  файла  и о разделах каталога мы поговорим
         чуть позже,  но сначала мы рассмотрим структуру  дисков,  которые
         форматировались  разными  способами  под управлением операционной
         системы MS-DOS.
              Ниже в разделах описываются форматы стандартных гибких  дис-
         ков и жестких (постоянных) дисков.  Отметим, что в некоторых сис-
         темах все 40-трековые форматы, поддерживаемые операционной систе-
         мой  MS-DOS ,  также могут использоваться с 3,5-дюймовыми гибкими
         дисками. В таких системах дисковод для 3,5-дюймового гибкого дис-
         ка  должен  рассматриваться аппаратурой системы,  как 40-трековый
         дисковод для 5,25-дюймовых гибких дисков,  что  часто  случается,
         когда дисководы 3,5-дюймовых гибких дисков подсоединены к сущест-
         вующим в системах IBM PC, IBM PC/XT и IBM PC/AT контроллерам гиб-
         ких дисков. 80-трековые форматы, поддерживаемые операционной сис-
         темой  MS-DOS,  однако,  обычно  не   являются   заменяемыми  для
         3,5-дюймовых и 5,25-дюймовых гибких дисков.


                Структура 40-трековых, односторонних, 5,25-дюймовых
                                   гибких дисков

              На рис. 11-1 представлен вид структуры 40-трековых, односто-
         ронних, 5,25-дюймовых гибких дисков. Здесь показаны треки (дорож-
         ки) и сектора.  Приведен пример расположения данных файла на дис-
         ке.  В  первой  части  иллюстрации  показана   структура   диска,
         отформатированного  по  восемь  секторов на один трек.  Во второй
         части иллюстрации показана только правая часть формата  - отличие
         трека 0 для диска,  отформатированного по девять секторов на один
         трек.
              В первой  части  рисунка  11-1 представлен простейший формат
         диска,  работающего под управлением операционной системы  MS-DOS.
         Поскольку  все  стандартные дисковые форматы операционной системы
         MS-DOS поддерживают размер сектора в 512 байтов,  мы можем  легко
         проверить информацию, подсчитав общую емкость диска следующим об-
         разом:

                                      - 11-4 -

                       40 треков х 8 секторов х 512 байтов =
                   =  163840 байтов ( = 160 Кбайт общей емкости)

              Общую емкость диска можно проверить, сравнив с результатами,
         выводимыми на экран при использовании команд FORMAT и CHKDSK.

                   Формат  расположения восьми секторов на треке
               ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
               ¦ \   Трек                                         ¦
               ¦   \               1         2         3        3 ¦
               ¦     \   0123456789012345678901234567890123456789 ¦
               ¦Cектор ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
               ¦   1   ¦ BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEE ¦
               ¦   2   ¦ FXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEE ¦
               ¦   3   ¦ FXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEE ¦
               ¦   4   ¦ DXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEE ¦
               ¦   5   ¦ DXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEE ¦
               ¦   6   ¦ DXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEE ¦
               ¦   7   ¦ DXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEEE ¦
               ¦   8   ¦ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEEEE ¦
               АДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

                   Формат расположения девяти секторов на треке
          ЪДДДДДДДДДДДДДДДДДДДДДДДДДД¬
          ¦ \    Трек                ¦
          ¦   \            ...     3 ¦    B = запись начальной загрузки
          ¦     \   01     ...     9 ¦    D = элемент каталога
          ¦Cектор ЪДДДДДДДДДДДДДДДДДДґ    F = таблица  размещения файла
          ¦   1   ¦ BX     ...     E ¦
          ¦   2   ¦ FX     ...     E ¦     Только для целей данного
          ¦   3   ¦ FX     ...     E ¦          примера:
          ¦   4   ¦ FX     ...     E ¦    X = секторы, содержащие
          ¦   5   ¦ FX     ...     E ¦        данные файла
          ¦   6   ¦ DX     ...     E ¦    E = пустые секторы
          ¦   7   ¦ DX     ...     E ¦
          ¦   8   ¦ DX     ...     E ¦
          ¦   9   ¦ DX     ...     E ¦
          АДДДДДДДБДДДДДДДДДДДДДДДДДДЩ
             Рис. 11-1. Схема расположения информации на 40-трековых,
                    односторонних, 5,25-дюймовых гибких дисках

              Диск, отформатированный по девять секторов на дорожку (толь-
         ко для операционной системы MS-DOS версий 2.0 и выше),  похож  на
         диск,  отформатированный  по восемь секторов на дорожку.  Разница
         заключается в добавленном девятом секторе в конце  каждого трека,
         что меняет общую емкость диска. Проверить это можно тоже, получив
         общую емкость диска по представленной ниже формуле  и  сравнив  с
         результатами, выводимыми на экран при использовании команд FORMAT
         и CHKDSK.

                    40 треков х 9 секторов х 512 байтов ={F10}
                   =  184320 байтов ( = 180 Кбайт общей емкости)

              Другое различие  между  восьмисекторными  и девятисекторными
         форматами дисков заключается в количестве секторов  FAT  (таблицы
         размещения  файлов).  Несмотря на то,  что оба формата имеют один

                                      - 11-5 -
         сектор начальной загрузки и четыре сектора  каталога,  количество
         секторов  таблицы FAT больше в девятисекторовом формате.  Диски с
         форматом по восемь секторов на трек имеют два сектора FAT (секто-
         ры 2 и 3 дорожки 0).  Диски с форматом по десять секторов на трек
         имеют четыре сектора FAT (секторы со 2 по 5 дорожки 0). Необходи-
         мость  в дополнительном количестве секторов FAT на  десятисектор-
         ных дисках возникает вследствие наличия дополнительного файлового
         пространства,  образованного 40 дополнительными секторами (по од-
         ному сектору на треке).

                Структура 40-трековых, двухсторонних, 5,25-дюймовых
                                   гибких дисков

              Двухсторонние 40-трековые  гибкие  диски,  отформатированные
         под управление операционной системой MS-DOS,   имеют то же  коли-
         чество секторов FAT (пропорциональное),  что и односторонние гиб-
         кие диски, но большее количество секторов каталога, что позволяет
         увеличить  общее  число файлов,  расположенных на диске.  В обоих
         форматах двухсторонних дисков (в восьми- и в девятитрековом)  под
         каталоговые   сектора  распределяется  семь  секторов.  Структура
         двухсторонних 40-трековых дисковых форматов,  поддерживаемых опе-
         рационной системой MS-DOS,  представлена на рисунке 11-2. В левой
         части рисунка показан восьмисекторный  формат. В правой части ри-
         сунка показан десятисекторный формат.
              Структура диска, представленная на рис.11-2, очень похожа на
         структуру диска, представленную на рис.11-1. Отметим, что на всех
         двухсторонних гибких дисках,  отформатированных  под  управлением
         операционной  системы  MS-DOS,  сохраняемая  на  треке информация
         всегда начинается со стороны 0 сектора 1 и далее располагается до
         последнего  сектора  на  этом треке.  Потом происходит переход на
         сторону 1 и информация продолжает располагаться с первого по пос-
         ледний сектор трека.  Далее снова происходит возврат на сторону 0
         и информация начинает помещаться  с  первого  сектора  следующего
         трека  и  так далее вплоть до последнего сектора последнего трека
         стороны 1.  Отметим также,  что расположение секторов каталога  и
         секторов  таблицы  FAT  отличаются  от односторонних дисков.  Оба
         двухсторонних формата имеют по одному треку начальной  загрузки и
         пропорциональное количество таких же секторов таблицы FAT,  как и
         односторонние диски.  Число секторов каталога,  однако, для обоих
         форматов двухсторонних дисков возрастает до семи.  И снова, срав-
         нивая результаты представленных ниже двух расчетных формул с  ре-
         зультатами выполнения программы CHKDSK,  мы можем проверить общую
         емкость двух форматов 40-трековых двухсторонних гибких дисков:
                 40 треков х 8 секторов х 512 байтов х 2 стороны =
                   = 327680 байтов ( = 320 Кбайт общей емкости)

                 40 треков х 9 секторов х 512 байтов х 2 стороны =
                   =  368640 байтов ( = 360 Кбайт общей емкости)


                Структура 80-трековых, двухсторонних, 5,25-дюймовых
                                   гибких дисков

              Операционная система MS-DOS версии 3.0 позволила использова-
         ние нового формата 5,25-дюймовых гибких дисков, известного обычно

                                      - 11-6 -
         под названием формат "повышенной емкости". Этот формат предостав-
         ляет возможность хранения данных общим  объемом  в  1,2  миллиона
         байтов  и  требует использования специального дисковода 80-треко-
         вых,  5,25-дюймовых гибких дисков. Этот формат повышенной емкости
         невозможен с 3,5-дюймовыми гибкими дисками.
              Структура описываемого  формата  очень  похожа  на  прежние,
         только что рассмотренные форматы.  Увеличение емкости формата за-
         висит от использования гибких дисков с удвоенным количеством тре-
         ков  (80)  и  форматирование большего количества секторов (15) на
         одном треке. На рисунке 11-3 представлена структура этого формата
         и  сделан  акцент  на старших номерах секторов каталога и таблицы
         FAT, которые обеспечивают увеличение емкости.

           Формат  расположения восьми     Формат  расположения девяти
               секторов на треке               секторов на треке
         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬   ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         ¦       \   Трек             ¦   ¦       \   Трек             ¦
         ¦         \         ...    3 ¦   ¦         \         ...    3 ¦
         ¦           \   012 ... 6789 ¦   ¦           \   012 ...    9 ¦
         ¦      Cектор ЪДДДДДДДДДДДДДДґ   ¦      Cектор ЪДДДДДДДДДДДДДДґ
         ¦         1   ¦ BXX ... XXXX ¦   ¦         1   ¦ BXX ...      ¦
         ¦         2   ¦ FXX ... XXXX ¦   ¦         2   ¦ FXX ...      ¦
         ¦         3   ¦ FXX ... XXXX ¦   ¦         3   ¦ FXX ...      ¦
         ¦Сторона  4   ¦ DXX ... XXXX ¦   ¦Сторона  4   ¦ FXX ...      ¦
         ¦   0     5   ¦ DXX ... XXXX ¦   ¦   0     5   ¦ FXX ...      ¦
         ¦         6   ¦ DXX ... XXXX ¦   ¦         6   ¦ DXX ...      ¦
         ¦         7   ¦ DXX ... XXXX ¦   ¦         7   ¦ DXX ...      ¦
         ¦ ________8   ¦ DXX ... XXXX ¦   ¦         8   ¦ DXX ...      ¦
         ¦         1   ¦ DXX ... XEEE ¦   ¦ ________9   ¦ DXX ...      ¦
         ¦         2   ¦ DXX ... XEEE ¦   ¦         1   ¦ DXX ...      ¦
         ¦         3   ¦ XXX ... XEEE ¦   ¦         2   ¦ DXX ...      ¦
         ¦Сторона  4   ¦ XXX ... XEEE ¦   ¦         3   ¦ DXX ...      ¦
         ¦   1     5   ¦ XXX ... XEEE ¦   ¦Сторона  4   ¦ XXX ...      ¦
         ¦         6   ¦ XXX ... XEEE ¦   ¦   1     5   ¦ XXX ...      ¦
         ¦         7   ¦ XXX ... EEEE ¦   ¦         6   ¦ XXX ...      ¦
         ¦         8   ¦ XXX ... EEEE ¦   ¦         7   ¦ XXX ...      ¦
         АДДДДДДДДДДДДДБДДДДДДДДДДДДДДЩ   ¦         8   ¦ XXX ...      ¦
                                          ¦         9   ¦ XXX ...      ¦
                                          АДДДДДДДДДДДДДБДДДДДДДДДДДДДДЩ
         B = запись начальной загрузки      Только для целей данного
         D = элемент каталога                       примера:
         F = таблица  размещения файла      X = секторы, содержащие
                                                данные файла
                                            E = пустые секторы

           Рис. 11-2.  Схема расположения  информации  на  40-трековых,
                    двухсторонних, 5,25-дюймовых гибких дисках
              Ниже приведена расчетная формула, которую можно использовать
         для  проверки результатов,  выводимых на экран программой CHKDSK,
         когда она обрабатывает 80-трековый, 5,25-дюймовый гибкий диск:

                80 треков х 15 секторов х 512 байтов х 2 стороны =
            = 1228800 байтов (= 1200 Кбайт или 1,2 Мбайт общей емкости)

              Операционная система  MS-DOS версии 3.0 обеспечила поддержку
         3,5-дюймовых гибких дискет.  3,5-дюймовые дискеты могут тоже фор-
         матироваться в такие же односторонние или двухсторонние форматы с

                                      - 11-7 -
         восемью или девятью секторами на одном треке, как это было в слу-
         чае использования 40-трековых,  5,25-дюймовых дискет при условии,
         что физическое имя дисковода будет задано командой FORMAT.  Двух-
         сторонний, 80-трековый формат с 9 секторами на трек для  3,5-дюй-
         мовых  дискет был также впервые введен основной операционной сис-
         темой MS-DOS версии 3.0. На рис. 11-3 представлена структура дис-
         ка   в   этом  формате.  Этот  формат  предоставляет  возможность
         использования общей емкости памяти в 720 Кбайт:

                 80 треков х 9 секторов х 512 байтов х 2 стороны =
                   =  737280 байтов ( = 720 Кбайт общей емкости)

              3,5-дюймовые дискеты форматируются в формат 720  Кбайт путем
         задания  имени логического дисковода командой FORMAT без парамет-
         ров, определяющих формат. Используемый логический дисковод, кото-
         рый  был специально создан для форматирования 720 Кбайтных гибких
         дискет,  создается во время начальной загрузки, если представлен-
         ная ниже строка включена в файл CONFIG.SYS:
                              DEVICE = DRIVER.SYS/D:х

         где х представляет собой физический номер дисковода для 80-треко-
         вых,  3,5-дюймовых  дискет  (0=A:,  1=B:  и  так   далее).   Файл
         DRIVER.SYS представляет собой драйвер,  поставляемый операционной
         системой MS-DOS версий 3.2 и старше.  При начальной загрузке сис-
         темы файл DRIVER.SYS создает логический дисковод,  используя сле-
         дующую имеющуюся букву,  обозначающую дисковод, и приписывает эту
         букву заданному физическому дисководу.  После того,  как диск был
         отформатирован в формат 720 Кбайт,  его можно считывать и на него
         можно производить запись, используя имя физического дисковода или
         соответствующее имя логического дисковода.
             Операционная система MS-DOS версии  3.30  внедрила  еще  один
         формат повышенной емкости для 3,5-дюймовых дискет,  содержащих по
         80 треков,  каждый из которых отформатирован на 18 секторов  (см.
         рисунок 11-3).  Этот формат предоставляет общую емкость дискеты в
         1,44 Мбайта при использовании дисков, протестированных на 2 Мбай-
         та.  Специальные дисководы, предназначенные для 3,5-дюймовых дис-
         кет, впервые введенные в персональных компьютерах типа IBM  PS/2,
         требуются для поддержания этого формата.  1,44-Мегабайтные диско-
         воды поддерживают также формат 720-Кбайтных дисков:
                80 треков х 18 секторов х 512 байтов х 2 стороны =
                  = 1474560 байтов ( = 1,44 Мбайта общей емкости)



                                      - 11-8 -











             Формат расположения пятнадцати     Формат  расположения девяти
                   секторов на треке                 секторов на треке
               (5,25-дюймовые  дискеты)            (3,5-дюймовые дискеты)
             ЪДДДДДДДДДДДДДДДДДДДДДДДДДДД¬   ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
             ¦       \   Трек            ¦   ¦       \   Трек             ¦
             ¦         \         ...   7 ¦   ¦         \         ...    7 ¦
             ¦           \   012 ... 789 ¦   ¦           \   012 ...   89 ¦
             ¦      Cектор ЪДДДДДДДДДДДДДґ   ¦      Cектор ЪДДДДДДДДДДДДДДґ
             ¦         1   ¦ BXX ... XEE ¦   ¦         1   ¦ BXX ...   XX ¦
             ¦         2   ¦ FXX ... XEE ¦   ¦         2   ¦ FXX ...   XX ¦
             ¦         3   ¦ FXX ... XEE ¦   ¦         3   ¦ FXX ...   XX ¦
             ¦         4   ¦ FXX ... XEE ¦   ¦Сторона  4   ¦ FXX ...   XX ¦
             ¦         5   ¦ FXX ... XEE ¦   ¦   0     5   ¦ FXX ...   XX ¦
             ¦         6   ¦ FXX ... XEE ¦   ¦         6   ¦ FXX ...   XX ¦
             ¦Сторона  7   ¦ FXX ... EEE ¦   ¦         7   ¦ FXX ...   XX ¦
             ¦   0     8   ¦ FXX ... EEE ¦   ¦         8   ¦ DXX ...   XX ¦
             ¦         9   ¦ FXX ... EEE ¦   ¦ ________9   ¦ DXX ...   XX ¦
             ¦        10   ¦ FXX ... EEE ¦   ¦         1   ¦ DXX ...   XX ¦
             ¦        11   ¦ FXX ... EEE ¦   ¦         2   ¦ DXX ...   XX ¦
             ¦        12   ¦ FXX ... EEE ¦   ¦         3   ¦ DXX ...   XX ¦
             ¦        13   ¦ FXX ... EEE ¦   ¦Сторона  4   ¦ DXX ...   XX ¦
             ¦        14   ¦ FXX ... EEE ¦   ¦   1     5   ¦ DXX ...   XX ¦
             ¦ _______15   ¦ FXX ... EEE ¦   ¦         6   ¦ XXX ...   XX ¦
             ¦         1   ¦ DXX ... EEE ¦   ¦         7   ¦ XXX ...   XX ¦
             ¦         2   ¦ DXX ... EEE ¦   ¦         8   ¦ XXX ...   XX ¦
             ¦         3   ¦ DXX ... EEE ¦   ¦         9   ¦ XXX ...   XX ¦
             ¦         4   ¦ DXX ... EEE ¦   АДДДДДДДДДДДДДБДДДДДДДДДДДДДДЩ
             ¦         5   ¦ DXX ... EEE ¦
             ¦         6   ¦ DXX ... EEE ¦
             ¦Сторона  7   ¦ DXX ... EEE ¦
             ¦   1     8   ¦ DXX ... EEE ¦
             ¦         9   ¦ DXX ... EEE ¦
             ¦        10   ¦ DXX ... EEE ¦
             ¦        11   ¦ DXX ... EEE ¦
             ¦        12   ¦ DXX ... EEE ¦
             ¦        13   ¦ DXX ... EEE ¦
             ¦        14   ¦ DXX ... EEE ¦
             ¦        15   ¦ XXX ... EEE ¦
             АДДДДДДДДДДДДДБДДДДДДДДДДДДДЩ










                                      - 11-9 -
                Формат  расположения восемнадцати секторов на треке
                              (3,5-дюймовые дискеты)
                      ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
                      ¦       \   Трек               ¦
                      ¦         \           ...    7 ¦
                      ¦           \   012   ...   89 ¦
                      ¦      Cектор ЪДДДДДДДДДДДДДДДДґ
                      ¦         1   ¦ BXX   ...   XX ¦
                      ¦         2   ¦ FXX   ...   XX ¦
                      ¦         3   ¦ FXX   ...   XX ¦
                      ¦         4   ¦ FXX   ...   XX ¦
                      ¦         5   ¦ FXX   ...   XX ¦
                      ¦         6   ¦ FXX   ...   XX ¦
                      ¦         7   ¦ FXX   ...   XX ¦
                      ¦         8   ¦ DXX   ...   XX ¦
                      ¦Сторона  9   ¦ DXX   ...   XX ¦
                      ¦   0    10   ¦ DXX   ...   XX ¦
                      ¦        11   ¦ DXX   ...   XX ¦
                      ¦        12   ¦ DXX   ...   XX ¦
                      ¦        13   ¦ DXX   ...   XX ¦
                      ¦        14   ¦ DXX   ...   XX ¦
                      ¦        15   ¦ DXX   ...   XX ¦
                      ¦        16   ¦ FXX   ...   XX ¦
                      ¦        17   ¦ FXX   ...   XX ¦
                      ¦ _______18   ¦ FXX   ...   XX ¦
                      ¦         1   ¦ FXX   ...   XX ¦
                      ¦         2   ¦ FXX   ...   XX ¦
                      ¦         3   ¦ FXX   ...   XX ¦
                      ¦         4   ¦ FXX   ...   XX ¦
                      ¦         5   ¦ FXX   ...   XX ¦
                      ¦         6   ¦ FXX   ...   XX ¦
                      ¦         7   ¦ FXX   ...   XX ¦
                      ¦         8   ¦ FXX   ...   XX ¦
                      ¦Сторона  9   ¦ FXX   ...   XX ¦
                      ¦   1    10   ¦ FXX   ...   XX ¦
                      ¦        11   ¦ FXX   ...   XX ¦
                      ¦        12   ¦ FXX   ...   XX ¦
                      ¦        13   ¦ FXX   ...   XX ¦
                      ¦        14   ¦ FXX   ...   XX ¦
                      ¦        15   ¦ FXX   ...   XX ¦
                      ¦        16   ¦ XXX   ...   XX ¦
                      ¦        17   ¦ XXX   ...   XX ¦
                      ¦        18   ¦ XXX   ...   XX ¦
                      АДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДЩ
         B = запись начальной загрузки      Только для целей данного
         D = элемент каталога                       примера:
         F = таблица  размещения файла      X = секторы, содержащие
                                                данные файла
                                            E = пустые секторы

           Рис. 11-3.  Схема расположения  информации  на  80-трековых,
            двухсторонних,  5,25-дюймовых  и 3,5-дюймовых гибких дисках
                               повышенной плотности


                             Сектор начальной загрузки

              Самый первый сектор на диске, отформатированном под управле-
         нием операционной системы MS-DOS,  всегда определяется,  как "за-

                                      - 11-10 -
         пись начальной загрузки". Эта запись содержит короткую программу,
         которая автоматически загружается в память,  когда диск использу-
         ется  для загрузки операционной системы MS-DOS после подачи пита-
         ния в систему или после сброса системы. Далее эта программа гово-
         рит   компьютеру  где  ему  искать  на  диске  файлы,  содержащие
         программы операционной системы MS-DOS. После нахождения этих фай-
         лов  программа  начальной загрузки загружает эти файлы в память и
         передает управление операционной системе MS-DOS.  Поскольку коли-
         чество  файлов операционной системы MS-DOS и способ,  которым они
         сохраняются, могут быть различными в зависимости от типа реализа-
         ции   (например,   для   персональных   компьютеров   типа   "IBM
         PC", "COMPAQ"  или "CompuPro"),  содержимое  записи начальной за-
         грузки тоже может варьироваться.
              В целях сохранения логичности сектор начальной загрузки все-
         гда задается первым на отформатированном диске, независимо от то-
         го, собираетесь ли вы сделать этот диск "диском начальной загруз-
         ки" или "диском, содержащим одни только данные".
              Первые три байта записи начальной загрузки  всегда  содержат
         команду перехода.  При начальной загрузке команда перехода прика-
         зывает системе обойти первую часть этой записи и перейти к  прог-
         рамме начальной загрузки. По описанию операционной системы MS-DOS
         версии 2.00 27 байтов записи  начальной  загрузки,  расположенные
         между  начальной командой перехода и программой начальной загруз-
         ки,  содержат информацию о формате диска. Обращаясь к этой группе
         данных, программы могут получить почти всю информацию по формати-
         рованию,  необходимую для диска.  В таблице 11-1 представлено со-
         держимое  блока информации о форматировании находящегося в записи
         начальной загрузки.
              Информация о  форматировании в записи начальной загрузки мо-
         жет оказаться очень нужной при определении формата  диска. Запись
         начальной  загрузки  исходно создается,  когда диск форматируется
         командой FORMAT (для гибких дисков) или командой FDISK (для жест-
         ких дисков). Часть информации о форматировании в записи начальной
         загрузки под названием  "OEM  and  Version"  ("Фирма-изготовитель
         комплектующего  оборудования  и  версия")  обычно содержит номера
         версии и реализации использовавшейся операционной  системы MS-DOS
         для создания записи начальной загрузки.  Если,  например,  версия
         3.3 операционной системы IBM-DOS использовалась для  форматирова-
         ния диска это поле будет содержать информацию "IBM 3.3".  Осталь-
         ные элементы информации по  форматированию  из  записи  начальной
         загрузки  представляют  собой набор (надмножество) из блока пара-
         метров BIOS (блок ВРВ) (см.  главу 6), поскольку он уже существо-
         вал, во время форматирования диска.
              Последние три элемента информации  о  форматировании  (коли-
         чество  секторов на треке,  количество головок и количество скры-
         тых) секторов определяются расчетным путем и эти данные  вставля-
         ются в запись начальной загрузки в процессе форматирования диска.
              Программа READFMT,  представленная листингом 11-1,  содержит
         описание способа, при помощи которого информация о форматировании
         из записи начальной загрузки считывается с диска  и  отображается
         на экране дисплея.  Кроме элементов форматирования,  которые при-
         сутствуют в записи начальной загрузки,  программа  READFMT  также
         осуществляет  расчет  нескольких других элементов,  относящихся к
         форматированию,  таких как общая системная память,  общая  память
         данных и общая емкость дисков. Программа READFMT отображает полу-
         ченные результаты на экране дисплея.


                                     - 11-11 -
                                                         Таблица 11-1
                   Информация по форматированию, содержащаяся в
                             записи начальной загрузки
         ДДДДДДДДВДДДДДДДДДДДВДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Смещение¦ Смещение  ¦        ¦
         (деся-  ¦(шестнадца-¦ Размер ¦    Содержание  поля
         тичное) ¦ тиричное) ¦        ¦
         ДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
           0     ¦   00      ¦3 байта ¦Ближний переход к программе
                 ¦           ¦        ¦начальной загрузки
         ДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
           3     ¦   03      ¦8 байтов¦Название фирмы-изготовителя    ¦
                 ¦           ¦        ¦оборудования и его версия   И  ¦
         ДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДД¬  Н  ¦
          11     ¦   0B      ¦1 слово ¦Количество байтов    Б   ¦  Ф  ¦
                 ¦           ¦        ¦в секторе            Л   ¦  О Ф¦
          13     ¦   0D      ¦1 байт  ¦Количество секторов  О   ¦  Р О¦
                 ¦           ¦        ¦в кластере           К   ¦  М Р¦
          14     ¦   0E      ¦1 слово ¦Число зарезервиро-      B¦  А М¦
                 ¦           ¦        ¦ванных секторов      П  I¦  Ц А¦
          16     ¦   10      ¦1 байт  ¦Количество таблиц    А  O¦  И Т¦
                 ¦           ¦        ¦FAT                  Р  S¦  Я И¦
          17     ¦   11      ¦1 слово ¦Количество элементов А   ¦    Р¦
                 ¦           ¦        ¦в каталоге           М   ¦  П О¦
          19     ¦   14      ¦1 слово ¦Количество логичес-  Е   ¦  О В¦
                 ¦           ¦        ¦ких секторов         Т   ¦    А¦
          21     ¦   15      ¦1 байт  ¦Байт описателя среды Р   ¦    Н¦
          22     ¦   16      ¦1 слово ¦Количество секторов  О   ¦    И¦
                 ¦           ¦        ¦таблицы FAT          В   ¦    Ю¦
         ДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДЩ     ¦
          24     ¦   18      ¦1 слово ¦Количество секторов в треке    ¦
          26     ¦   1A      ¦1 слово ¦Количество  головок            ¦
          28     ¦   1С      ¦1 слово ¦Количество  скрытых  секторов  ¦
         ДДДДДДДД†ДДДДДДДДДДДБДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          30     ¦   1E     416 байтов¦Программа начальной загрузки   ¦
         446     ¦   1BE     16 байтов¦Информация о разделении        ¦
                 ¦           ¦        ¦памяти                         ¦
         462     ¦   1CE     50 байтов¦Остальная часть программы      ¦
                 ¦           ¦        ¦ начальной загрузки            ¦
         ДДДДДДДДБДДДДДДДДДДДБДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ


              * Для операционной системы МS-DOS версий 2.Х = 3-байто-
         вый переход
                Для операционной системы МS-DOS версий 2.Х  =  2-байтовый
         короткий переход плюс операция NOP.
              ** Общее количество элементов в корневом каталоге.
              *** Байты описателя среды не всегда являются действительны-
         ми, как для операционной системы MS-DOS версии 2.0
              **** Относится только к жестким дискам,  содержащим  таблицы
         начальной загрузки; эта область не используется на гибких дисках.




                                     - 11-12 -
                          Листинг 11-1. Программа READFMT
         ----------------------------------------------------------------
            PAGE          50,132
            TITLE         READFMT.ASM/.EXE
           .SALL          ; подавить выдачу макрорасширений в программе
           .8086          ; пользоваться  только командами
                          ; микропроцессоров 8086/8088
         ;*************************************************************
         ;**  Программа READFMT  Версии 1.00
         ;**     Настоящая  программа считывает сектор начальной заг-
         ;** рузки с любого диска, расшифровывает обнаруженный в  за-
         ;** писи  начальной загрузки блок параметров BIOS(BPB),и вы-
         ;** водит на экран дисплея эту информацию и некоторую другую
         ;** расчетную информацию.
         ;**
         ;**     Примечание: во время создания этой программы  перек-
         ;** лючатель  LINK  "/СР:1"  должен использоваться для того,
         ;** чтобы во время загрузки программы, ей распределялось  бы
         ;** действительно  нужное  количество  памяти.  По умолчанию
         ;** максимальным объемом распределяемой памяти в том случае,
         ;** когда "/СР:1" не задан, является вся память  выше  точки
         ;** загрузки  программы, что вызывает завершение программы с
         ;** выдачей сообщения об ошибке, потому что она не в состоя-
         ;** нии сделать дополнительное  перераспределение памяти  во
         ;** время своей работы.
         ;*************************************************************
         ;
         ;   СОСТАВ:
         INCLUDE       stdequ.inc  ; включить стандартный файл равенств
         INCLUDE       stdmac.inc  ; включить стандартный файл макросов
         INCLUDELIB    stdlib.lib  ; включить  стандартную библиотеку
                                   ; STDLIB.LIB во временное редакти-
                                   ; рование
                                   ;
         ; Заявки на программы обращения к внешним  библиотекам,
         ; находящиеся в STDLIB.LIB
         EXTRN  dosv2con:NEAR      ; получить и вывести на экран текущую
                                   ; версию DOS
         EXTRN  dosver:NEAR        ; дать текущую версию  DOS
         EXTRN  bin2dec2:NEAR      ; вывести на экран дисплея DX:AX в
                                   ; десятичных цифрах кода ASCII
                                   ; (если  регистр  DX=),  регистр AX
                                   ; считается беззнаковым
         ;
         ; - - - - - -  ИНИЦИАЛИЗАЦИЯ - - - - - - - - - - - - - - - - - -
         ;
         .MODEL   SMALL            ; малая модель
         .STACK  2048              ; создать 2-Кбайтовый  стек
         ;
         ;*************************************************************
         ;  ГЛАBHАЯ  ПРОГРАММА
         ;
         .DATA
         ;
         ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;      Cсылки и обращения к компонентам записи начальной заг-
         ; рузки,  считанной  с диска и помещенный в блок памяти, осу-

                                     - 11-13 -
         ; ществляются при помощи ES.  Обращения  производятся  только
         . компонентам  блока  "Блок параметров BIOS"(BPB); первые три
         ; байта и все данные расположенные после  области  блока  ВРВ
         ; записи начальной загрузки, игнорируются.
         ;
         bootrecord     STRUC
         BootJump       db     3 DUP (?) ; исходная команда перехода
         OEMstring      db     8 DUP (?) ; фирма-изготовитель и версия
                                         ; операционной системы MS-DOS
         SectorBytes    dw     ?         ; количество байтов в секторе
         ClusterSec     db     ?         ; количество секторов в кластере
         ReservedSec    dw     ?         ; зарезервированные сектора
         FATcopies      db     ?         ; количество копий таблицы FAT
         DirEntries     dw     ?         ; количество элементов в
                                         ; корневом каталоге
         TotalSectors   dw     ?         ; общее количество  секторов
                                         ; диска (100% диска)
         MediaDescrip   db     ?         ; описатель среды
         FATsectors     dw     ?         ; количество секторов, занятых
                                         ; одной таблицей FAT
         TrackSectors   dw     ?         ; количество секторов в треке
         Heads          dw     ?         ; количество головок
         HiddenSectors  dw     ?         ; количество скрытых секторов
         bootrecord     ENDS
         ;
         .CODE
         ;
         ; Cохранение локальных данных (храните эти определения
         ; в сегменте  программы)
         ;
         DSsave         dw    seg DGROUP ; память для регистра DS
         ;
         .DATA
         PSPseg         dw     ?         ; сегмент PSP (Префикс про-
                                         ; граммного сегмента)
         .CODE
         main     PROC NEAR              ; начало основного процесса
         ;
         ;***********************************************************
         ; ЗАПУСК ПРОГРАММЫ
         ;***********************************************************
              mov   ds,DSsave           ; инициализация DS
              mov   ax,es               ; получить адрес сегмента PSP
              mov   word ptr PSPseg,ax  ; ....... и сохранить его
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Bывести на экран дисплея сообщение о запуске
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

               @DisStr Start1_Msg       ; вывести на экран дисплея
                                        ; сообщение о запуске
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Получить номер/имя дисковода
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                  mov   di,80h                  ; командная строка
                  cmp   byte ptr es:[di],0      ; есть ли параметры?
                  je    get_default_drive       ; нет, получить дисковод

                                     - 11-14 -
                                                ; по умолчанию
                  cmp   byte ptr es:[di+3],':'  ; присутствует ли двое-
                                                ; точие?
                  jne   get_default_drive       ; нет, получить дисковод
                                                ; по умолчанию
         get_disk_drive:                        ; получить дисковод в
                                                ; командной строке
                  xor   ah,ah                   ; очистить AH
                  mov   al,byte ptr es:[di+2]   ; получить заданный
                                                ; дисковод
                  cmp   al,">"                  ; использовалась ли
                                                ; переадресовка?
                  je    get_default_drive       ; да, получить дисковод
                                                ; по умолчанию
                  cmp   al,61h                  ; дисковод задан буквами
                                                ; верхнего регистра?
                  jge   convert_upper           ; да, выполнить преобра-
                                                ; зование из символов
                                                ; ASCII верхнего регистра
                  sub   al,40h                  ; иначе  выполнить  из
                                                ; символов ASCII  нижнего
                                                ; регистра
                  jmp   short test_drive        ; и продолжить
         convert_upper:
                  sub   al,60h                  ; преобразование символов
                                                ; ASCII верхнего регистра
         test_drive:
                  cmp   al,1                    ; число меньше единицы?
                  jl    bad_drive               ; да, выйти на сообщение
                                                ; об ошибке
                  dec   al                      ; иначе выполнить А:=0,
                                                ; ...... В: = 1  и т.д.
                  cmp   al,25                   ; результат предыдущeй
                                                ; команды > 25 (>Z:)?
                  jg    bad_drive               ; да, выйти на сообщение
                                                ; об ошибке
                  jmp   short drive_used        ; иначе  сохранить
                                                ; указанный дисковод
         get_default_drive:
                  mov   ah,19h                  ; получить дисковод
                                                ; по умолчанию
                  @DosCall
         drive_used:
                  mov   byte ptr DiskDrive,al   ; сохранить дисковод
                  jmp   short drive_end         ; и продолжить
         bad_drive:
                  @DisStr BadDrive_Msg          ; иначе вывести на экран
                                                ; сообщение об ошибке
                                                ;(синтаксической ошибке)
                  jmp   terminate               ; и выйти в DOS
         drive_end:
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Cчитывание в память информации сектора начальной загрузки.
         ; После возвращения ES:DI(ES:0) указывает на  блок  памяти,
         ; содержащий  запись  начальной загрузки.
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

                                      - 11-15 -
         .DATA
         BootSeg   dw    ?                      ; сохранение адреса
                                                ; сегмента блока памя-
                                                ; ти содержащего копию
                                                ; записи начальной
                                                ; загрузки
         .CODE
                   mov   bx,40h                 ; распределить 1024 байта
                   call  memalloc               ; распределить  блок
                   jnc   read_boot              ; продолжить, если не
                                                ; было ошибок
                   call  mem_err_handler        ; иначе обработать ошибку
                   jmp   terminate              ; и выйти в DOS
                                                ;
         ;                                      ;
         read_boot:
                   mov   word ptr BootSeg,ax    ; сохранить адрес сег-
                                                ; мента
                   push  ax                     ; и  сохранить  его
                   mov   al,byte ptr DiskDrive  ; получить  дисковод
                                                ; для считывания
                   xor   ah,ah                  ; очистить AH
                   pop   ds                     ; получить адрес сег-
                                                ; мента нового блока
                   mov   dx,0                   ; считать логический
                                                ; сектор 0
                   mov   cx,1                   ; считать в один сектор
                   mov   bx,0                   ; поместить данные в DS:0
                   jnt   25h                    ; считать диск
                   jc    read_boot_error        ; выйти, если произошла
                                                ; ошибка
                   popf                         ; очистить флаги, про-
                                                ; толкнутые в стек во
                                                ; время прерывания
                                                ; "int 25h"
                   mov   ds,DSsave              ; повторная инициали-
                                                ; зация области DS
                   mov   ax,word ptr BootSeg    ; получить адрес сегмен-
                                                ; та начальной загрузки
                   mov   es,ax                  ; и инициализировать в
                                                ; него ES
                   xor   di,di                  ; со смещением 0
                   jmp   end_read_boot           ; и продолжить
         ;
         read_boot_error:
                   popf                         ; очистить флаги, про-
                                                ; толкнутые в стек во
                                                ; время прерывания
                                                ; "int 25h"
                   mov   ds,DSsave              ; повторная инициали-
                                                ; зация области DS
                   @DisStr ReadError_Msg       ; выйти с выдачей
                   jmp   terminate              ; сообщения об ошибке
         ;
         end_read_boot:
         ;

                                     - 11-16 -
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;     Проверить,  чтобы  считанная  запись начальной загрузки
         ; содержала  нужную  нам  информацию.  Если  диском  является
         ; 160-Кбайтный или 320-Кбайтный гибкий диск, запись начальной
         ; загрузки  будет  содержать  нужную нам информацию блока ВРВ
         ; (это может оказаться правильным также для некоторых нестан-
         ; дартных форматов дисков , в случае чего таблица FAT  должна
         ; считываться  с  целью  получения  байта идентификатора (ID)
         ; формата. Этот байт требуется для определения формата  диска
         ; (является  ли  диск  160-Кбайтным  для -версии операционной
         ; системы DOS 1.0 или; 360-Кбайтным для  версии  операционной
         ; системы DOS 1.1.
         ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;

                   mov    bx,20h              ; распределить 512 бай-
                                              ; ... тов (32 параграфа)
                   call   memalloc            ; распределить блок памяти
                   jnc    read_fat            ; продолжить,  если ошибок
                                              ; не было
                   call   mem_er_handler      ; иначе перейти на про-
                                              ; грамму обработки ошибок
                   jmp    terminate           ; и выйти в DOS
         ;
         read_fat:
         .DATA
         FATSeg    dw     ?                     ; адрес  сегмента  из
                                                ; таблицы  FAT
         .CODE
                   mov    word ptr FATseg,ax    ; сохранить адрес сег-
                                                ; мента FAT
                   push   ax                    ; и сохранить его
                   mov    al,byte ptr DiskDrive ; получить дисковод для
                                                ; считывания
                   xor    ah,ah                 ; очистить  AH
                   pop    ds                    ; получить адрес сег-
                                                ; мента нового блока
                   mov    dx,1                  ; очистить логический
                                                ; сектор 1
                   mov    cx,1                  ; считать в один сектор
                   mov    bx,0                  ; сохранить данные в DS:0
                   jnt    25h                   ; читать диск
                   jnc    process_FAT           ; продолжить, если ошибок
                                                ; не было
                   popf                         ; иначе, сбросить флаги
                   mov    ds,DSsave             ; повторная инициализа-
                                                ; ция области DS
                   @DisStr ReadError_Msg        ; выйти с сообщением об
                                                ; ошибке
                   mov    ax,word ptr FATSeg    ; получить адрес сегмента
                                                ; начальной  загрузки
                   call   memfree               ; освободить блок
                   jnc    end_fat_err           ; выйти, если не было
                                                ; ошибок
                   call   mem_err_handler       ; иначе, вывести на
                                                ; экран дисплея сообще-
                                                ; ние об  ошибке

                                     - 11-17 -
         end_fat_err:
                   jmp    terminate             ; выйти в DOS
         ;
         process_FAT:
                   popf                         ; очистить флаги, про-
                                                ; толкнутые в стек во
                                                ; время прерывания
                                                ; "int 25h"
                   mov    ds,DSsave             ; повторная инициализа-
                                                ; ция области DS
                   mov    ax,word ptr FATSeg    ; получить адрес сегмента
                                                ; начальной  загрузки
                   mov    es,ax                 ; и инициализировать ES
                   xor    di,di                 ; в него со смещением 0
         .DATA
         FAT_ID    db     ?                     ; байт идентификации
                                                ; (ID) из таблицы FAT
         .CODE

                   mov    al,byte ptr es:[di]   ; получить элемент О
                                                ; таблицы FAT
                   mov    byte ptr FAT_ID,at    ; и сохранить его как байт
                   call   memfree               ; освободить сегмент
                                                ; таблицы FAT
                                                ; (адрес находится в ES)
                   jnc    comp_byte_id          ; продолжить, если не
                                                ; было ошибок
                   call   mem_err_handler       ; иначе вывести на экран
                                                ; сообщение  об ошибке
                   jmp    terminate             ; выход в операционную
         ;                                      ; систему DOS
         comp_byte_id:
                   mov    ax,word ptr BootSeg   ; указать на сегмент
                                                ; начальной загрузки
                   mov    es,ax                 ; ES:0 указать на
                   xor    di,di                 ; запись  начальной
                                                ; загрузки
                   mov    al,byte ptr FAT_ID    ; получить идентифика-
                                                ; тор (ID) таблицы FAT
                   cmp    al,byte ptr es:[di].MediaDescrip ; и сравнить
                                                ; с битом ID
                                                ; в записи  начальной
                                                ; загрузки
                   jne    chk_dos1_fmt          ; если иначе, то решить
                   jmp    end_read_fat          ; иначе, продолжить
         chk_dos1_fmt:
                   cmp    al,0FEh               ; это  160-Кбайтный
                                                ; гибкий диск?
                   je     init_dos1_fmt         ; да, инициализировать
                                                ; запись начальной
                                                ; загрузки
                   cmp    al,0FFh               ; иначе, это 320-Кбайт-
                                                ; ный  гибкий  диск?
                   je     init_dos1_fmt         ; да, инициализировать
                                                ; запись начальной
                                                ; загрузки

                                     - 11-18 -
         .DATA
         UnknownMedia db "Невозможно определить формат диска."
                      db  "Возможно, это диск не "
                      db  "операционной системы MS-DOS.",CR,LF,"$"
         .CODE
                   @DisStr UnknownMedia        ; вывести на экран
                                               ; сообщение об ошибке
                   jmp     terminate           ; выход в операционную
                                               ; систему DOS
         init_dos1_fmt:
                ; Инициализировать элементы  формата, которые одинаковы
                ; для 160-Кбайтных и 320-Кбайтных форматов дисков:
                   mov     word ptr es:[di+3],"D"   ; передать по буквам
                                                    ; "DOS  1.X"
                   mov     word ptr es:[di+4],"O"   ; в поле ОЕМ и
                   mov     word ptr es:[di+5],"S"   ; версии DOS записи
                   mov     word ptr es:[di+6]," "   ; начальной загрузки
                   mov     word ptr es:[di+7],"1"
                   mov     word ptr es:[di+8],"."
                   mov     word ptr es:[[b]di].SectorBytes,512 ; коли-
                                              ; чество байтов в секторе
                   mov     word ptr es:[di].ReservedSec,1   ; зарезер-
                                                  ; вированные секторы
                   mov     word ptr es:[di[.FATcopies,2     ; количест-
                                                  ; во копий таблицы FAT
                   mov     word ptr es:[di].FATsectors,1    ; количество
                                                  ; секторов таблицы  FAT
                   mov     word ptr es:[di].TrackSectors,8  ; количество
                                                  ; секторов в треке
                   mov     word ptr es:[di].HiddenSectors,0 ; количество
                                                  ; скрытых секторов
                   cmp     al,0FEh      ; это гибкий 160-Кбайтный диск?
                   je      init_160K    ; да, инициализировать запись
                                        ; начальной загрузки
                   cmp     al,0FFh      ; иначе, это гибкий 320-Кбайтный
                                        ; диск?
                   je      init_320K    ; да, инициализировать запись
                                        ; начальной загрузки
         ;
         init_160K:
                   mov word ptr es:[di.9],"0"          ; это версия
                                                       ; "DOS 1.0"?
                   mov     word ptr es:[di.10],"?"          ;
                   mov     byte ptr es:[di].ClusterSec,1    ; количество
                                                            ; секторов в
                                                            ; кластере
                   mov     word ptr es:[di].DirEntries,64   ; количество
                                                            ; элементов
                                                            ; в каталоге
                   mov     word ptr es:[di].TotalSektors,320 ; общее ко-
                                                             ; личество
                                                             ; секторов
                                                             ; на диске
                   mov     byte ptr es:[di].MediaDescrip,0FEh ; описатель
                                                              ; носителя
                   mov     word ptr es:[di].Heads,1  ; количество головок
                   jmp          end_read_fat

                                     - 11-19 -
         ;
         init_320K:
                   mov     word ptr es:[di.9],"1"      ; это версия
                                                       ; "DOS 1.1"?
                   mov     word ptr es:[di.10],"?"
                   mov     byte ptr es:[di].ClusterSec,2    ; количество
                                                            ; секторов в
                                                            ; кластере
                   mov     word ptr es:[di].DirEntries,112  ; количество
                                                            ; элементов
                                                            ; в каталоге
                   mov     word ptr es:[di].TotalSektors,640 ; общее ко-
                                                             ; личество
                                                             ; секторов
                                                             ; на диске
                   mov     byte ptr es:[di].MediaDescrip,0FFh ; описатель
                                                              ; носителя
                   mov     word ptr es:[di].Heads,2  ; количество головок
                   jmp          end_read_fat
         ;
         end_read_FAT:
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;      Вычислить величины, отсутствующие в  блоке  параметров
         ; BIOS.  Примечание:  ES:DI  (смещение 0) должен указывать на
         ; начало записи начальной загрузки, считанной в  память.  Все
         ; расчетные значения сохраняются в сегменте данных.
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         get_new_values:
                    xor    di,di    ; очистить DI
         ;
         ; Рассчитать общее количество секторов, используемых для всех
         ; копий таблицы  FAT.
                xor    ah,ah                          ; очистить AH
                mov    al,byte ptr es:[di].FATcopies  ; получить копии
                                                      ; таблицы FAT
                xor    dx,dx                          ; очистить DX
                mov  bx,word ptr es:[di].FATsectors   ; получить число
                                                      ; секторов таблицы
                                                      ; FAT
                mul    bx                             ; умножить на
                                                      ; это число
                mov    word ptr TotalFATSec,ax      ; сохранить ре-
                                                    ; зультат в 1 слове
         ;
         ; Рассчитать общее  количество  секторов,  используемых всеми
         ; элементами  каталога
                mov    ax,word ptr es:[di].DirEntries ; получить общее
                                                      ; количество эле-
                                                      ; ментов корневого
                                                      ; каталога

                    mov    bx,word ptr DirEntBytes    ; получить размер
                                                      ; в байтах эле-
                                                      ; ментов каталога
                    xor    dx,dx                      ; очистить  DX
                    mul    bx                         ; умножить

                                     - 11-20 -
                    mov    bx,word ptr                ;
         es:[di].SectorBytes     ; получить количество байтов в секторе
                    xor    dx,dx                      ; очистить  DX
                    div    bx                         ; разделить
                    mov    word ptr DirSectors,ax     ; сохранить ре-
                                                      ; зультат в 1 слове
         ;
         ; Вычислить  общее  количество цилиндров
               mov    ax,word ptr es:[di].TotalSectors  ; получить общее
                                                   ; количество секторов
               mov    bx,word ptr es:[di].TrackSectors   ; получить ко-
                                            ; личество секторов в  треке
               xor    dx,dx                          ; очистить  DX
               div    bx                           ; и разделить на него
               mov    bx,word ptr es:[di].Heads ; получить число головок
               xor    dx,dx                        ; очистить  DX
               div    bx                           ; и разделить на него
               cmp    word ptr es:[di].HiddenSectors ; скрытые сектора?
               je     store_cyl                      ; нет, теперь мы
                                     ; имеем общее количество цилиндров
               mov    cx,word ptr es:[di].HiddenSectors ; иначе, скрытые
                                                         ; сектора = 1
               cmp    cx,word ptr es:[di].TrackSectors    ; цилиндр?
               je     add_cyl              ; да
               mov    ax,0                 ; иначе, произошла ошибка
               jmp    short store_cyl      ;
         add_cyk:
                     add   ax,1     ; добавить дополнительный цилиндр
         store_cyl:
                     mov   word ptr Cylinders,ax ; сохранить результат
         ;
         ; Получить  общее количество байтов на всем (100%) диске
                     mov   ax,word ptr es:[di].TotalSectors ; получить
                                           ; общее количество секторов
                     xor   dx,dx                          ; очистить DX
                     mov   bx,word ptr es:[di].SectorBytes  ; получить
                                         ; количество байтов в секторе
                     mul   bx                      ; и умножить на него
                     mov   word ptr TotalBytes,ax  ; и сохранить резуль-
                                          ; тат, состоящий из двух слов
                     mov   word ptr TotalBytes.2,dx ; из AX и DX
         ;
         ; Получить общее количество секторов данных (в которых могут
         ; сохраняться файлы)
                     mov   ax,word ptr es:[di].TotalSectors ; получить
                                           ; общее количество секторов
         xor   dx,dx                                    ; очистить  DX
                    sub   ax,word ptr es:[di].ReservedSec ; вычесть
                                           ; зарезервированные секторы
                    sub   ax,word ptr TotalFATSec  ; вычесть общее
                                     ; количество секторов таблицы FAT
                                             ; (все копии таблицы FAT)
                sub   ax,word ptr DirSectors    ; вычесть  секторы
                                                ; корневого каталога
                mov   word    ptr DataSectors,ax ; и сохранить результат
         ;
         ; Получить количество байтов в кластере

                                     - 11-21 -
                 xor   ah,ah                           ; очистить AH
                 mov   al,byte ptr es:[di].ClusterSec  ; получить секторы
                                                       ; кластера
                 xor   dx,dx                           ; очистить DX
                 mov   bx,word ptr es:[di].SectorBytes ; получить байты
                                                       ; сектора
                 mul   bx                      ; и умножить на это число
                 mov   word ptr ClusterBytes,ax  ; и сохранить результат
         ;
         ; Получить общее количество секторов
                 mov   ax,word ptr DataSectors ; получить секторы данных
                 xor   bh,bh                      ; очистить BH
                 mov   bl,byte ptr es:[di].ClusterSec ; получить секторы
                                                      ; кластера
                 xor   dx,dx                     ; очистить DX
                 div   bx             ; разделить на количество секторов
                 mov   word ptr TotalClusters,aх ; и сохранить результат
                                                 ; в слове
         ;
         ; Получить количество битов в элементе таблицы FAT.
         ; Всегда 12 битов, если общее количество кластеров = 4,085 или
         ; меньше.
         ; Всегда 16 битов, если общее количество кластеров больше
         ; 4,085.
                 cmp   word ptr TotalClusters,4085       ; общее коли-
                                       ; чество кластеров больше 4085?
                 jle   got_entry_size  ; нет используйте 12-битовое
                                       ; значение по умолчанию
                 mov   al,16           ; иначе - 16-битовое значение
                 mov   byte ptr FATentryBits,al ; и сохранить значение
         got_entry_size:
         ;
         ; Получить общее количество байтов данных (используемых байтов)
                 mov   ax,word ptr DataSectors   ; получить общее коли-
                                                 ; чество секторов
                 xor   dx,dx,                    ; очистить DX
                 mov   bx,word ptr es:[di].SectorBytes ; получить байты
                                                 ; сектора
                 mul   bx                     ; и умножить на это число
                 mov   word ptr DataBytes,ax  ; и  сохранить результат
                                              ; в 2 словах
                 mov   word ptr DataBytes,2,dx   ; из AX и DX
         ;
         ; Вычислить регистр  диска  в  килобайтах или мегабайтах
                 mov   ax,word ptr TotalBytes    ; получить общее коли-
                                               ; чество байтов на диске
                 mov   dx,word ptr TotalBytes.2  ; ... (двойное слово)
                 mov   cx,1024                   ; установить делитель
                 div   cx           ; и получить значение в килобайтах
                 mov   word ptr KBytes,ax        ; сохранить значение
                 cmp   ax,1000              ; вычислить в Мегабайтах?
                 jl    dis_info             ; нет,  мы  закончили
                 mov   bx,1000              ; иначе установить делитель
                 xor   dx,dx                ; очистить регистр DX
                 div   bx                   ; и получить мегабайты
                 mov   word ptr Mbytes,ax   ; сохранить главное значение
                 mov   word ptr Mbytes2,dx  ; и сохранить часть, если

                                      - 11-22 -
                                            ; таковая имеется
                 cmp   dx,0                 ; есть ли часть?
                 je    megabytes_end        ; нет,  мы  закончили
                 mov   ax,dx                ; иначе установить делимое
                 mov   bx,10                ; установить  делитель
                 xor   dx,dx                ; очистить регистр DX
         ;
         ; Цикл удаления хвостовых нулей
         compress_loop:
                     div   bx               ; разделить регистр AX на 10
                     cmp   dx,0                ; есть ли остаток?
                     jne   megabytes_end       ; да, мы закончили
                     mov   word ptr Mbytes2,ax ; иначе сохранить новое
                                               ; сжатое значение
                     jmp   short compress_loop ; и снова  повторить
         megabytes_end:
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Отобразить на экране дисплея информацию о диске
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         dis_info:
                   @DisStr Start2_Msg ; отобразить на экране дисплея
                               ; первую часть сообщения о  дисководе
                   xor   ah,ah                 ; очистить регистр AH
                   mov   al,byte ptr DiskDrive ; и вывести на экран
                                               ; имя дисковода
                   inc   al         ; дать ему используемый номер
                   add   al,40h     ; преобразовать его  в  буквы
         ;                          ; ... ASCII верхнего регистра
                   @DisChr al       ; и вывести его на экран
                   @DisChr ':'      ; после него поставить двоеточие
                   @Newline
         ;
                   @DisStr OEM_Msg ; вывести на экран сообщение OЕМ
                                   ; (о фирме-производителя и версии)
                   push  di                     ; сохранить DI
                   mov   di,bootrecord.OEMstring ; указать строку ОЕМ
                   mov   cx,8          ; установить счетчик  символов
         more_char:                       ; ввести дисплейный цикл
                   mov   al,byte ptr es:[di]    ; получить символ
                   @DisChr al   ; отобразить символ на экране дисплея
                   inc   di            ; указать на следующий символ
                   dec   cx            ; счетчик десятичных символов
                   cmp   cx,0          ; все сделаны ?
                   jg    more_char     ; нет, вывести следующий символ
                   @NewLine            ; иначе, мы закончили
                   pop   di                     ; восстановить DI
         ;
                   @DisStr  MeliaDescrip_Msg  ; отобразить на экране
                                              ; описатель  носителя
                   xor   ah,ah,               ; очистить регистр AH
                   mov   al,byte ptr es:[di].MediaDecrip ; считать
                                              ; байтовое значение
                   @DisNum ax,16,2            ; отобразить две шест-
                                              ; надцатиричные цифры
                   cmp   byte ptr es:[di].MediaDescrip,0F8h ;жесткий диск?
                   je fixed_disk   ; да, отобразить на экране сообщение

                                      - 11-23 -

                   @DisStr RemovableMedia_Msg ; иначе - сменный диск
                   jmp   short media_size     ; и теперь сделать размер
                                              ; в Kb/Mb
         fixed_disk:
                      @DisStr FixedMedia_Msg     ; отобразить на экране
                                         ; сообщение о жестком носителе
         media_size:
                      cmp   word ptr Mbytes,0  ; показывать в Мегабайтах
                      je    show_kilobytes     ; нет, в Килобайтах
                      mov   ax,word ptr Mbytes ; да, задать в Мегабайтах
                      @DisNum ax,10,1,0        ; вывод десятичного числа
                                               ; без знака
                      cmp   word ptr Mbytes2,0


         ; есть ли часть?
                      je    done_mbytes        ; нет, мы закончили
                      @DisChr '.'              ; иначе, вывести на
                                               ; экран десятичную точку
                      mov   ax,word ptr Mbytes2 ; задать часть в Мбайтах
                      @DisNum  ax,10,1,0       ; вывод десятичного числа
                                               ; без знака
         done_mbytes:
                      @DisChr 'M'                ; вывести на экран сим-
                                                 ; вол "М"  (Мегабайт)
                      jmp   short done_media     ; и мы закончили
         show_Kilobytes:
                      mov  ax,word  ptr Kbytes   ; получить значение в
                                                 ; Килобайтах
                      @DisNum ax,10,1,0          ; вывод десятичного
                                                 ; числа без знака
                      @DisChr 'K' ; вывести на экран символ "К" (Кбайт)

         done_media:
                      @DisStr Media_Msg        ; вывести на экран конец
                                               ; сообщения
                      @NewLine
         ;
                      @DisStr Cylinders_Msg     ; отобразить на экране
                           ; дисплея общее количество цилиндров (треков)
                      cmp   word ptr Cylinders,0   ; была ли ошибка?
                      jne   show_cyl        ; нет, отобразить на экране
                                   ; дисплея общее количество цилиндров
                      @DisChr '?'  ; иначе, вывести на экран символ
                                   ; "знак вопроса"
                      jmp   short end_cyl         ; и закончить
         show_cyl:
                      mov   ax,word ptr Cylinders ; получить значение
                      @DisNum ax,10,1,0           ; и отобразить его на
                                                  ; экране дисплея
         end_cyl:
                @NewLine
         ;
                @DisStr Heads_Msg         ; отобразить на экране
                                          ; количество головок
                mov   ax,word ptr es:[id].Heads ; получить значение слова

                                      - 11-24 -
                @DisNum ax,10,1,0    ; вывод десятичного числа без знака
                @NewLine
         ;
                @DisStr TrackSectors_Msg   ; отобразить на экране
                            ; дисплея количество секторов в треке
                mov   ax,word ptr es:[di].TrackSector  ; получить
                                                 ; значение слова
                @DisNum ax,10,1,0  ; вывод десятичного числа без знака
                @NewLine
         ;
                @DisStr SectorBytes_Msg          ; отобразить на экране
                                 ; дисплея  количество байтов в секторе
                mov   ax,word ptr es:[id].SectorBytes ; получить значе-
                                                      ; ние слова
                @DisNum ax,10,1,0   ; вывод десятичного числа без знака
                @NewLine
         ;
                @DisStr НiddenSectors_Msg ; отобразить на экране
                           ; дисплея количество скрытых секторов
                mov ax,word ptr es:[di].HiddenSectors ; получить зна-
                                                        ; чение слова
                @DisNum ax,10,1,0   ; вывод десятичного числа без знака
                cmp  word ptr es:[di].HiddenSectors,0 ; есть ли
                                           ; скрытые  сектора?
                je    hidden_done          ; нет, мы закончили
                @DisStr PartitionInfo_Msg  ; иначе, дать инфор-
                                           ; мацию о разделении
         hidden_done:
               @NewLine
         ;
               @DisStr TotalSectors_Msg     ; отобразить на экране
                      ; дисплея общее  количество  секторов  диска
               mov   ax,word ptr es:[di].TotalSectors   ; получить
                                                  ; значение слова
               @DisNum ax,10,1,0 ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr TotalBytes_Msg     ; отобразить на экране
                      ; дисплея общее количество байтов на диске
               mov   ax,word ptr TotalBytes ; получить  значение,
                                  ; состоящее из двух слов и ...
               mov   dx,word ptr TotalBytes.2 ; поместить его в
                                            ; регистры  AX и DX
               call  bin2dec2               ; и вывести на экран
                                            ; результат DX:AX
               @NewLine
         ;
               @DisStr ReservedSec_Msg    ; отобразить на экране
                  ; дисплея количество зарезервированных секторов
               mov   ax,word ptr es:[di].ReservedSec    ; получить
                                                   ; значение слова
               @DisNum ax,10,1,0  ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr FATsectors_Msg    ;отобразить на
                               ; экране дисплея количество секторов
                               ; в одной таблице FAT

                                      - 11-25 -
               mov   ax,word ptr es:[di].FATsectors ; получить
                                                   ; значение слова
               @DisNum ax,10,1,0 ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr FATcopies_Msg    ; отобразить на  экране дисплея
                                        ; количество копий таблицы FAT
               xor   ah,ah          ; очистить AH
               mov   al,byte ptr es:[di].FATcopies  ; получить
                                                ; значение байта
               @DisNum  ax,10,1,0 ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr TotalFATsectors_Msg  ; отобразить на экране
                           ; дисплея общее количество секторов для
                           ; всех таблиц FAT
               mov   ax,word ptr TotalFATSec  ; получить значение слова
               @DisNum ax,10,1,0    ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr DirEntries_Msg         ; отобразить на экране
                   ; дисплея количество элементов корневого каталога
               mov   ax,word ptr es:[di].DirEntries ; получить значение
                                                    ; слова
               @DisNum ax,10,1,0    ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr DirSektors_Msg       ; отобразить  на экране
                               ; дисплея  общее количество секторов
                               ; корневого каталога
               mov   ax,word ptr DirSectors ; получить значение слова
               @DisNum ax,10,1,0   ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr DataSectors_Msg      ; отобразить на экране
         ;            ; дисплея общее  количество  секторов данных
               mov   ax,word ptrDataSectors ; получить значение слова
               @DisNum ax,10,1,0   ; вывод десятичного числа без знака
               @NewLine
         ;

               @DisStr ClusterSectors_Msg        ; отобразить на экране
                             ; дисплея количество секторов  в  кластере
               xor   ah,ah                       ; очистить AH
               mov   al,byte ptr es[di].ClusterSec ; получить значение
                                                 ; байта
               @DisNum  ax,10,1,0   ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr ClusterBytes_Msg          ; отобразить на экране
                                ; дисплея количество байтов в  кластере
               mov   ax,word ptr ClusterBytes ; получить значение слова
               @DisNum ax,10,1,0   ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr Totalclusters-Msg     ; отобразить на экране
                                   ; дисплея общее количество кластеров

                                      - 11-26 -
               mov   ax,word ptr Totalclusters ; получить значение слова
               @DisNum ax,10,1,0   ; вывод десятичного числа без знака
               @NewLine
         ;
               @DisStr FATentrySize_Msg         ; отобразить на экране
                         ; дисплея размер каждого элемента таблицы  FAT
               xor   ah,ah                      ; очистить AH
               mov   al,byte ptr FATentryBits   ; получить значение
                                                ; байта
               @DisNum ax,10,1,0   ; вывод десятичного числа без знака
               @DisStr Bits_Msg                ; указать, что значение
                                               ; задано в битах
               cmp   byte ptr FATentryBits,12  ; определить, сколько
                                               ; имеется байтов
               jg   dis_two_bytes
               @DisStr SmallFAT_Msg   ; элемент таблицы FAT = 1,5 байта
               jmp  short show_fat_done
         dis_two_bytes:
                      @DisStr LargeFAT_Msg     ; элемент таблицы
                                               ; FAT = 2 байта
         show_fat_done:
         ;
                 @NewLine
         ;
                 @DisStr DataBytes_Msg  ; отобразить на экране дисплея
                                    ; количество байтов данных на диске
                  mov   ax,word ptr DataBytes     ; получить значение
                  mov   dx,word ptr DataBytes.2   ; состоящее из двух
                              ; слов и поместить его в регистры AX и DX
                  call  bin2dec2       ; и вывести его на экран дисплея
                  @NewLine
         ;
                  push   es            ; сохранить текущее значение ES
                  mov   ax,word ptr BootSeg   ; получить адрес сегмента
                                              ; распределенного блока
                  mov   es,ax                     ; и назначить ему ES
                  call  memfree                   ; освободить блок
                  pop   es                  ; восстановить значение ES
         ;
         terminate:
                  @ExitToDOS                ; завершить  программу
         ;
         ;************************************************************
         ; Конец программы
         ;************************************************************
         main   ENDP               ; конец главного процесса
         ;
         ;************************************************************
         ; Запуск стандартных программ
         ;*************************************************************
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++
         ; MEM_ERR_HANDLER: Описатель ошибки распределения памяти
         ; (освобождения памяти) изменения размера.
         ;BXОД:       AX = код ошибки
         ;            BX = максимальный доступный блок памяти
         ;                 (если код  ошибки  =  8)
         ;            ЕX = адрес сегмента распределенного блока

                                      - 11-27 -
         ;                 (если код ошибки = 9)
         ;
         ;ВЫХОД:        ни  один  (все  регистры восстановлены)
         ;
         ;Вызываемые стандартные программы : нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - -
         mem_err_handler PROC  NEAR
         ;
                      cmp   ax,7     ; очищенные от мусора управляющие
                                     ;  блоки  памяти ?
                      jne   mem_error8     ; нет, продолжить проверку
         .DATA
         NranshedMemErr_Msg db  "Ошибка распределения памяти:"
                      db"разрушены управляющие блоки памяти.",CR,LF,"$"
         .CODE
                 @DisStr TrashedMemErr_Msg    ; да, выйти с сообщением
                 ret                          ; возврат
         ;
         mem_error8:
                      cmp   ax,8             ; недостаточно памяти ?
                      jne   mem_error9       ; нет, продолжить проверку
         .DATA
         InsuffMemErr_Msg  db  "Ошибка распределения памяти:"
                           db  "недостаточно памяти",CR,LF
                           db  "Hаибольший доступный блок памяти = $"

         .CODE
                  @DisStr InsuffMemErr_Msg   ; да, выйти с сообщением
                  @DisNum bx,10,1,0          ; доступен
                  @NewLine           ; вывести на экран пустую строку
                  ret                        ; возврат
         ;
         mem_error9:
                   cmp   ax,9      ; неправильный  адрес блока памяти?
                   jne   mem_err_unknown    ; нет, неизвестная причина
         .DATA
         IncorrSegAddr_Msg db "Неправильный адрес сегмента для изменения"
                           db "изменения размера/освобождения.",CR,LF
                           db "Адрес сегмента = $"
         .CODE
                      @DisStr IncorrSegAddr_Msg   ; вывести на экран
                                       ; дисплея сообщение об ошибке
                      @DisNum es,16,4  ; вывести на экран дисплея адрес
                                       ; сегмента
                      @NewLine         ; вывести на экран пустую строку
                      ret              ; возврат
         ;
         mem_err_unknown:
         .DATA
         UnknownMemErr_Msg db "Hеизвестная  ошибка распределения/"
                 db "/изменения размера/освобождения памяти.",CR,LF,"$"
         .CODE
                 @DisStr UnknownMemErr_Msg  ; вывести на экран
                                            ; дисплея сообщение
                 ret
         ;
         mem_err_handler ENDP

                                      - 11-28 -
         ;
         ;;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; MEMALLOC: Распределить блок памяти заданного размера в
         ; параграфах  (1 параграф = 16 байтам).
         ;
         ;BXОД:       BX = размер в 16-байтовых параграфах запрошенного
         ;                 блока
         ;
         ;ВЫХОД:      "Успешно", если признак переноса = 0  при
         ;             AX = адрес сегмента распределенного
         ;             блока памяти (BX восстанавливается)
         ;             "Сбой",  если признак переноса = 1 при
         ;              AX = код ошибки
         ;                     7 = разрушены управляющие блоки памяти
         ;                     8 = недостаточно памяти
         ;              BX = наибольший  доступный блок памяти
         ;                   в параграфах
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - -- - -
         memalloc          PROC      NEAR
         ;
                      push  bp               ; сохранить указатель базы
                      push  bx               ; сохранить  регистр  BX
                      mov   bp,sp     ; инициализировать указатель базы
         ;
                      xor   al,al     ; очистить AL
                      mov   ah,48h    ; загрузить функцию распределения
                                      ; памяти
                      @DosCall        ; выполнить распределение памяти
                      jnc   end_memalloc     ; выйти, если нет ошибок с
                                     ; с адресом сегмента в регистре AX
                                     ; иначе, выйти с установленным
                                     ; признаком переноса;
                      mov   word ptr [bp],bx ; блок максимального размера
                                             ; (BX) и код ошибки в AX
         ;
         end_memalloc:
                      pop  bx         ; восстановить регистр BX
                      pop  bp         ; восстановить указатель базы
                      ret
         memalloc     ENDP            ;
         ;
         ;
         ;;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; MEMFREE:  Освобождение  памяти : освободить блок памяти,
         ;          распределенный ранее стандартной программой MALLOC
         ;
         ;BXОД:     ES = адрес сегмента  распределенного блока памяти
         ;
         ;ВЫХОД:   "Успешно", если признак переноса = 0
         ;          (ES восстанавливается)
         ;
         ;         "Сбой",  если признак переноса = 1 при
         ;          AX = код ошибки
         ;              7 = разрушены управляющие блоки памяти
         ;              9 = неправильный адрес (ES  восстанавливается)
         ;

                                      - 11-29 -
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - -- - -
         memfree      PROC      NEAR
         ;
                      push   bp      ; сохранить указатель базы
                      push   es
                      push   ax      ; сохранить регистр AX
                      mov    bp,sp   ; инициализировать указатель базы
         ;
                      xor    al,al             ; очистить AL
                      mov    ah,49h            ; загрузить функцию
                                               ; освобождения памяти
                      @DosCall         ; выполнить освобождение памяти
                      jnc    end_memfree  ; выход, если не было ошибок
                                        ; иначе, выйти с установленным
                                        ; признаком переноса
                      mov    word ptr [bp],ax  ; и код ошибки
                                               ;(в регистре AX)
         ;
         end_memfree:
                      pop    ax         ; восстановить регистр AX
                      pop    es
                      pop    bp         ; восстановить  указатель базы
                      ret
         ;
         memfree ENDP
         ;
         ;
         ;************************************************************
         ; Конец стандартной программы
         ;************************************************************
         ;
         .DATA                       ; переключение на сегмент данных
         ;************************************************************
         ; Hачало области сохранения данных
         ;************************************************************
         ;
         ; Переменные
         ;
         DiskDrive       db 0    ; рабочий дисковод (начальное
                                 ; значение = дисковод по умолчанию)
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Остальные (из вычисленных) переменные параметров дискового
         ; формата :
         Cylinders    dw   ?   ; общее количество цилиндров
         TotalBytes   dd   ?   ; общая емкость диска в байтах
         TotalFATSec  dw   ?   ; общее  количество  секторов  таблицы
                               ; FAT (всех  копий)
         DirEntBytes  dw   32  ; число байтов в элементе каталога
         DirSectors   dw   ?   ; сектора, занятые корневым каталогом
         DataSectors  dw   ?   ; общее количество секторов для хранения
                               ; файлов
         ClusterBytes dw   ?   ; количество  байтов  в  кластере
         TotalSectors dw   ?   ; общее количество кластеров
         FATentryBits db   12  ; количество битов в элементе таблицы FAT
         DataBytes    dd   ?   ; общее количество байтов данных (для
                               ; хранения файла)

                                     - 11-30 -
         Kbytes       dw   ?   ; общее количество килобайт (всего в
                               ; диске)
         Mbytes       dw   0   ; общее количество мегабайт (всего в
                               ; диске)
         Mbytes2      dw   0   ; и общее количество частей в мегабайтах
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Tекстовые сообщения
         ;
         Start1_Msg   db "Идентификатор формата диска операционной"
                      db   "системы  MS-DOS"
                      db "-- Версия 1.00",CR,LF,"$"
         Start2_Msg   db "BPB = Значение, извлеченное из записи"
                      db  " начальной загрузки;"
                      db "CAL = Вычисленное  значение",CR,LF,CR,LF
                      db "Информация о формате для дисковода $"
         ;
         OEM_Msg              db  "BPB: Форматируется при помощи:    $"
         SectorBytes_Msg      db  "BPB: Количество байтов в секторе: $"
         ClusterSectors_Msg   db  "BPB: Количество секторов в "
                              db  "кластере: $"
         ReservedSec_Msg      db  "BPB: Зарезервированные сектора:    $"
         FATcopies_Msg        db  "BPB: Копии таблицы FAT:            $"
         DirEntries_Msg       db  "BPB: Элементы корневого каталога:  $"
         TotalSectors_Msg     db  "BPB: Общее количество секторов "
                              db  "диска: $"
         MediaDescrip_Msg     db  "BPB: Описатель носителя:           $"
         FATsectors_Msg       db  "BPB: Сектора  таблицы FAT "
                              db  "(1 таблицы FAT):  $"
         TrackSectors_Msg     db  "BPB: Общее количество секторов "
                              db  "в цилиндре:  $"
         Heads_Msg            db  "BPB: Головки:                      $"
         HiddenSectors_Msg    db  "BPB: Скрытые  секторы:             $"
         ;
         TotalFATsectors_Msg  db  "CAL: Общее количество секторов "
                              db  "таблицы FAT: $"
         DirSectors_Msg       db  "CAL: Секторы каталога:             $"
         Total Bytes_Msg      db  "CAL: Общее количество байтов на"
                              db  "диске: $"
         Cylinders_Msg        db  "CAL: Общее  количество цилиндров:  $"
         DataSectors_Msg      db  "CAL: Общее количество секторов "
                              db  "данных:    $"
         TotalCluaters_Msg    db  "CAL: Общее количество кластеров:   $"
         ClusterBytes_Msg     db  "CAL: Количество байтов в кластере: $"
         FATentrySyze_Msg     db  "CAL: Размер элемента таблицы FAT:  $"
         DataBytes_Msg        db  "CAL: Общее количество байтов "
                              db  "данных:  $"
         ;
         FixedMedia_Msg       db  "(жесткий $"
         RemovableMedia_Msg   db  "(сменный $"
         Media_Msg            db  "носитель)$"
         Bits_Msg             db  "биты$"
         Bytes_Msg            db  "байты$"
         SmallFAT_Msg         db  "1,5 байта)$"
         LargeFAT_Msg         db  "(2 байта)$"
         CurrPartition_Msg    db  "(в пределах текущего разделения)$"
         PartitionInfo_Msg    db  "(информация о разделении)$"

                                      - 11-31 -
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Сообщения об ошибках:
         NonDOSerr_Msg db "Диск не может быть считан."
                       db "Вероятно, это не диск системы DOS.",CR,LF,"$"
         BadDrive_Msg  db "синтаксическая ошибка или"
                       db "заданный дисковод не разрешен.",CR,LF,"$"
         ReadError_Msg db "Общая ошибка при чтении диска."
                       db CR,LF,"$"
         UnknownErr_Msg db "Неизвестная ошибка - завершение."CR,LF,"$"
         ;
         ;
         ;***********************************************************
         ; Конец памяти данных
         ;***********************************************************
                   END    main                   ; конец программы

         ----------------------------------------------------------------



                         Таблицы разделения жесткого диска

              При ведении поддержки жестких или "фиксированных" дисков под
         управлением  операционной  системы MS-DOS версии 2.00 к сведениям
         по форматированию дисков был добавлен новый элемент: таблица раз-
         деления диска. Таблица разделения диска используется для описания
         того,  как диск разделяется на секции.  Эта таблица почти  всегда
         используется  только для жестких дисков (несменных) с емкостями в
         10 Мегабайт и выше. Таблица разделения диска состоит из 16 байтов
         информации,  начинающейся  со  смещением "шестнадцатиричное 01ВЕ"
         относительно начала записи начальной загрузки (первый  сектор  на
         диске). Структура таблицы разделения диска представлена в таблице
         11-2.
              Когда жесткий  диск разделен на одну или более частей коман-
         дой FDISK,  новая запись начальной загрузки с таблицей разделения
         диска  помещается  в  первый сектор каждой части.  Таким образом,
         диск с одной частью содержит  главную  таблицу  разделения  диска
         (она  хранится в записи начальной загрузки в самом первом секторе
         диске), а вторая таблица разделения диска в записи начальной  за-
         грузки помещается  в  первом секторе самой части.  Дополнительные
         части также содержат свои собственные записи начальной загрузки и
         таблицу разделения диска. Главная таблица разделения диска обнов-
         ляется каждый раз, когда используется команда FDISK для изменения
         разделения  диска, тогда  обновляется "поле состояния разделения"
         каждой отдельной части для того, чтобы оно отображало ее активное
         или неактивное состояние.



                                      - 11-32 -
                                                         Таблица 11-2
                        Структура таблица разделения диска
         ДДДДДДДДДВДДДДДДДДДДВДДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДД
         Смещение ¦ Смещение ¦        ¦           ¦
         (деся-   ¦ (шестнад-¦ Размер ¦ Название  ¦  Содержимое
         тичное)  ¦цатиричное)        ¦           ¦
         ДДДДДДДДД†ДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДД
            0     ¦    00    ¦ 1 байт ¦Состояние  ¦0 = неактивное
                  ¦          ¦        ¦разделения ¦80h = начально
                  ¦          ¦        ¦           ¦загружаемое,
                  ¦          ¦        ¦           ¦активное
            1     ¦    01    ¦ 1 байт ¦Начальная  ¦Целое число
                  ¦          ¦        ¦головка    ¦
            2     ¦    02    ¦ 1 слово¦Начальный  ¦См. примечание*
                  ¦          ¦        ¦сектор и   ¦
                  ¦          ¦        ¦цилиндр    ¦
            4     ¦    04    ¦ 1 байт ¦Тип разде- ¦1 = DOS с 12-битовой
                  ¦          ¦        ¦ления**    ¦    таблицей FAT
                  ¦          ¦        ¦           ¦4 = DOS с 16-битовой
                  ¦          ¦        ¦           ¦    таблицей FAT
                  ¦          ¦        ¦           ¦5 = расширенная DOS
            5     ¦    05    ¦ 1 байт ¦Конечная   ¦Целое число
                  ¦          ¦        ¦головка    ¦
            6     ¦    06    ¦ 1 слово¦Конечный   ¦См. примечание*
                  ¦          ¦        ¦сектор  и  ¦
                  ¦          ¦        ¦цилиндр    ¦
            8     ¦    08    ¦ 2 слова¦Начальный  ¦Целое число****
                  ¦          ¦        ¦абсолютный ¦
                  ¦          ¦        ¦сектор     ¦
           12     ¦    0С    ¦ 2 слова¦Количество ¦Целое число****
                  ¦          ¦        ¦секторов   ¦
         ДДДДДДДДДБДДДДДДДДДДБДДДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДД
               * Таблица разделения  диска начинается  со смещением
                 "шестнадцатиричное  01ВЕ" в исходной записи началь-
                 ной загрузки (1-ый абсолютный сектор жесткого  дис-
                 ка). Таблица  разделения диска содержит сведения о
                 начале начальной головки, цилиндра и сектора записи
                 начальной загрузки для активной части.
              ** Типы дополнительного разбиения используются некото-
                 рыми  производителями  для идентификации их системы
                 или для идентификации дисков большой емкости,  раз-
                 деленных на несколько логических дисков.
             *** Разделение  расширенной  операционной  системы  DOS
                 поддерживается только под управлением  операционной
                 системы MS-DOS версии 3.30 и выше.
            **** Цилиндр и сектор помещаются в битовую позицию - за-
                 кодированное обозначение, применяемое к начальному/
                 конечному цилиндру и сектору.
                       байт n                       байт n+1
         ¦<ДДДДДДДДДДДДДДДДДДДДДДДДДДДДД>†<ДДДДДДДДДДДДДДДДДДДДДДДДДДДДД>ґ
         ГДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДґ
         ¦ C ¦ C ¦ S ¦ S ¦ S ¦ S ¦ S ¦ S ¦ C ¦ C ¦ C ¦ C ¦ C ¦ C ¦ C ¦ C ¦
         АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
         Cтар-    Cтар-                    Млад-                     Млад-
          ший      ший                     ший                        ший
          бит      бит                     бит                        бит
              Два самых  старших  бита байта "n" предшествуют восьми битам
              байта "n+1" для  образования  10-битового  номера  цилиндра.
              Шесть младших битов байта "n" образуют номер сектора.

                                      - 11-33 -
              Несмотря на то,  что сектора таблицы FAT расположены следую-
         щими в последовательности секторов на диске, мы должны пропустить
         их и сначала обсудить сектора каталога.  Вы должны  понять  смысл
         секторов каталога для того,  чтобы понять, что размещается на ос-
         тальной части диска.

                                 Сектора каталога

              Сектора каталога содержат информацию  о  каталоге  для  всех
         файлов в корневом каталоге диска. Информация для файлов, содержа-
         щихся в подкаталогах,  хранится в файле подкаталога,  элемент для
         которой  помещен  в его родительский каталог (корневой или другой
         подкаталог). Когда вы выдаете команду DIR, информацию получают из
         секторов каталога, если считывается корневой каталог, или получа-
         ют из файла подкаталога,  описывающего текущий  подкаталог.  Пос-
         кольку  один сектор обычно занимает 512 байтов в длину,  мы можем
         легко вычислить,  что каждый элемент каталога имеет длину 32 бай-
         та. Общее количество элементов в корневом каталоге зависит от то-
         го,  сколько секторов каталога  задано.  Например,  односторонние
         гибкие  диски  имеют всего 64 элемента в корневом каталоге,  в то
         время,  как двухсторонние 40-трековые диски имеют 112 таких  эле-
         ментов, а двухсторонние 80-трековые 5,25-дюймовые диски имеют 224
         элемента в корневом каталоге.
              Для большинства жестких  дисков общее количество элементов в
         каталоге зависит от того, как был отформатирован диск. Каждый ва-
         риант разбиения жесткого диска имеет максимальное количество эле-
         ментов корневого каталога, которое соответствует регистрам частей
         разбиения.  Общее количество элементов корневого каталога опреде-
         ляет максимальное количество имен файлов,  которое может быть по-
         мещено в корневой каталог.  Это ограничение, однако, не распрост-
         раняется  на   подкаталоги.   Поскольку   элементы   подкаталога,
         соответствующие размещенным в подкаталогах файлам,  сами помещены
         в файл описания подкаталога,  не существует ограничений на  коли-
         чество файлов, помещенных в подкаталог; файл описания подкаталога
         может увеличиваться по мере надобности.
              Информация, содержащаяся в элементе каталога, подразделяется
         на шесть компонентов,  четыре из которых прямо или косвенно отно-
         сятся  к восстановлению стертых файлов.  На рисунке 11-4 показаны
         составляющие части элемента каталога,  длина каждого компонента и
         определение каждого из них.


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


                      Имя файла, тип файла и состояние файла

              Имя и тип файла состоят из 11 байтов, в которых представлено
         имя файла,  которому соответствует элемент каталога.  Каждый байт
         содержит символ АSCII.
              В операционной  системе MS-DOS версии 2.0 и выше имя файла в
         элементе каталога всегда относится к файлу,  хранящемуся в корне-
         вом каталоге. Поскольку имена подкаталогов считаются операционной
         системой MS-DOS файлами, они также имеют свои элементы в секторах
         каталога. Имя подкаталога, однако, содержит информацию в элементе
         каталога,  который слегка отличается от обычных  файлов.  Ниже  в
         разделах этой главы мы обсудим эти различия.

                                      - 11-34 -










                                                                 Атрибут
                 ¦<ДДДДДДДДДДДДДД 11 байтов ДДДДДДДДДДДДДДДДД>¦  (1 байт)
                 ¦<ДДДДДДДДДДД Имя файла ДДДДДДД>¦<ДДДТипДДДД>¦   
                 ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДВДД
                 ¦ xx  xx  xx  xx  xx  xx  xx  xx  xx  xx  xx ¦ xx ¦ < ДД¬
                 ¦  0   1   2   3   4   5   6   7   8   9  10 ¦ 11 ¦  >  ¦
                 АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДБД/   ¦
                 ¦   ¦                                                  ¦
                   ¦                                                     ¦
                  Состояние файла                                        ¦
                  или первый символ                                      ¦
                  имени файла                                            ¦
                                                                         ¦
           ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
           ¦
           ¦
           ¦        Зарезервировано операционной     Штамп о времени
           ¦              cистемой MS-DOS                и дате
           ¦      ¦<ДДДДДДДД (10 байтов) ДДДДДДДД>¦<ДДД (4 байта) ДД>¦
           ¦      ¦                               ¦<ДВремяД>¦<ДДатаД>¦
           ¦    ДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДВДДДДДДДДВДД
           АД> <  ¦ xx xx xx xx xx xx xx xx xx xx ¦ xx  xx  ¦ xx  xx ¦ < Д¬
                > ¦ 12 13 14 15 16 17 18 19 20 21 ¦ 22  23  ¦ 24  25 ¦  > ¦
               /ДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДБДДДДДДДДБД/  ¦
           ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
           ¦
           ¦      Начальный кластер    Размер файла
           ¦       ¦<Д(2 байта)Д>¦<ДДДДДД(4 байта)ДДДДДДД>¦
           ¦     ДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДД¬
           АДД> <  ¦  xx    xx   ¦  xx    xx    xx    xx  ¦
                 > ¦  26    27   ¦  28    29    30    31  ¦
                /ДДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДЩ

                     Рис. 11-4. Компоненты элемента каталога:
           1 - 11 байтов; 2 - атрибут (1 байт); 3 - имя файла; 4 -  тип;
           5 - состояние файла или первый символ имени файла; 6 - зарезерви-
           ровано для операционной системы MS-DOS (10 байтов); 7 - штамп о
           времени и дате (4 байта);  8 - время;  9 - дата; 10 - начальный
           кластер (2 байта); 11 - размер файла (4 байта)










                                      - 11-35 -
              Отметим также,  что  под  управлением  операционной  системы
         MS-DOS версии 2.0 и выше, максимальное число файлов или элементов
         каталога, которые гибкий диск может вместить (64, 112 и 224), со-
         ответствует только объему корневого каталога.  Поскольку все фай-
         лы, размещенные в подкаталоге, имеют каталоговые элементы в самом
         подкаталоге "файл",  не существует ограничений на количество фай-
         лов,  которые можно разместить на диске в пределах места, предус-
         мотренного конкретным типом диска или формата. Подробнее о подка-
         талогах и восстановлении файлов мы поговорим позже в  этой главе.
              При стирании файла  с диском происходят две вещи. Первым за-
         трагиваемым элементом является первый символ имени файла  в  эле-
         менте каталога.  Как показано на рисунке 11-4, первый байт в эле-
         менте каталога может либо указывать состояние файла,  либо предс-
         тавлять первый символ ASCII в имени файла.  Если элемент каталога
         не был использован с  момента  последнего  форматирования  диска,
         первый байт всегда установлен в значение "00". Таким образом опе-
         рационной системе MS-DOS нужно только считать первый байт элемен-
         та каталога для определения, может ли он использоваться. При соз-
         дании файла первый байт меняется и превращается в  первый  символ
         имени файла. Когда позднее файл стирается, первый байт меняется в
         шестнадцатиричное значение "E5".  Остальная информация в элементе
         каталога,  относящегося к стертому файлу, остается прежней. Когда
         вы просматриваете сектора диска в поисках информации  об элементе
         каталога стертого файла, то значение является для вас первым клю-
         чом к разгадке нужного элемента каталога.
              Вторым ключом,  конечно,  является  представление  остальной
         части имени файла и ее набор в формате ASCII.  Но  первый  символ
         шестнадцатиричного значения "E5" говорит о том, что элемент ката-
         лога представляет собой стертый файл.  Этот байт установлен в та-
         кое  значение для того,  чтобы операционная система MS-DOS знала,
         что элемент каталога освободился и в него может быть внесена  ин-
         формация  о  новом файле,  если этот элемент потребуется системе.
         Слава богу, разработчики операционной системы MS-DOS предусмотре-
         ли такой способ удаления файлов. Благодаря этому, мы теперь можем
         (в большинстве случаев) восстановить только что стертый файл.


                                      Атрибут

              Байт атрибута  содержит  информацию  об атрибутах размещения
         файла.  Атрибуты указывают, как операционная система MS-DOS ведет
         себя с файлом. В таблице 11-3 даны определения каждого атрибута и
         соответствующие шестнадцатиричные значения,  которые  могут  быть
         помещены в байт атрибута.  Каждый бит  этого байта определяет ка-
         кой-то конкретный атрибут и принимает значение "1" ,  когда  этот
         атрибут назначается файлу.
              Отметим, что файлу может быть назначено несколько атрибутов.
         Например,  если  файлу  назначены  атрибуты  "только  для чтения"
         (шестнадцатиричное значение 01) и "скрытый" ("только  для чтения")
         (шестнадцатиричное значение 02),  результирующее значение в байте
         атрибута будет представлять собой сумму обоих значений атрибутов,
         то есть шестнадцатиричное значение 03. Информация, содержащаяся в
         байте атрибута может быть,  а может не быть полезной для  восста-
         новления файла.  Например,  маловероятно, чтобы мы стали пытаться
         восстанавливать  файл,  атрибутом  которого было значение "только
         для чтения",  если только мы не пытаемся восстановить весь разру-
         шенный диск.  Обычно, мы не заботимся об атрибутах файла. Но, ес-
         ли, однако, мы восстанавливаем файл с атрибутом "скрытый", мы за-
         хотим изменить этот атрибут,  потому что в противном случае мы не

                                      - 11-36 -
         сможем  увидеть этого файла при выдаче команды DIR,  а,  следова-
         тельно,  не сможем убедиться в том,  что файл  был  восстановлен.
         Другая  причина,  по которой мы можем захотеть обращаться к байту
         атрибута файла, заключается в желании восстановления имени подка-
         талога.

                                                         Таблица 11-3
                            Определение атрибутов файла
         ДДДДДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Бит    ¦ Шестнадцатирич-¦       Атрибут
                ¦ ное  значение  ¦
         ДДДДДДД†ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         1-ый   ¦     80         ¦Не определен (в операционной системе
                ¦                ¦МS-DOS версии 3.0 и ниже)
         2-ой   ¦     40         ¦Не определен (в операционной системе
                ¦                ¦МS-DOS версии 3.0 и ниже)
         3-ий   ¦     20         ¦Состояние архива: Устанавливается
                ¦                ¦когда файл открывается и закрывается
                ¦                ¦и используется некоторыми утилитами
                ¦                ¦копирования и восстановления жесткого
                ¦                ¦диска
         4-ый   ¦     10         ¦Элемент подкаталога: Указывает на то,
                ¦                ¦что элемент каталога относится к под-
                ¦                ¦каталогу "файл"
         5-ый   ¦     08         ¦Метка тома: Указывает на то, что эле-
                ¦                ¦мент каталога  содержит  метку  тома
                ¦                ¦(только  для версий 2.0 и выше опера-
                ¦                ¦ционной  системы  DOS);  в остальной
                ¦                ¦части элемента  содержится  ненужная
                ¦                ¦информация
         6-ой   ¦     04         ¦Состояние системы:  используется  для
                ¦                ¦указания системных файлов, таких, как
                ¦                ¦те, что  используются  для  начальной
                ¦                ¦загрузки системы. Файлы начальной заг-
                ¦                ¦рузки  операционной  системы  MS-DOS
                ¦                ¦(например,   файлы   IBMDOS.СОМ    и
                ¦                ¦IBMBIO.СОМ ) должны иметь этот атрибут
                ¦                ¦установленным.
         7-ой   ¦     02         ¦Состояние "скрытый". Файл исключается
                ¦                ¦из нормального просмотра каталога
         8-ой   ¦     01         ¦Состояние "только для чтения": Файл не
                ¦                ¦может быть стерт.
         ДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                                 Начальный кластер

              Начальный кластер представляет собой 2-байтовый, 16-цифровой
         двоичный номер,  который определяет первую секцию диска,  занятую
         файлом.  Эта секция диска называется кластером.  Несмотря на  то,
         что на рисунке 11-1 изображен форматированный диск с точки зрения
         треков и секторов, операционная система MS-DOS в действительности
         рассматривает  диск  с точки зрения кластеров секторов,  а не от-
         дельных целых секторов.  Начальный кластер представляет собой ис-
         ходный "указатель" на первый сектор данных файла, а также на пос-

                                      - 11-37 -
         ледующие указатели в секторах таблицы FAT.  Считав этот начальный
         указатель  в элементе каталога,  операционная система MS-DOS про-
         должает считывать остальные указатели на  сектора данных файлов в
         секторе  таблицы  FAT.  Ниже мы будем говорить о секторах таблицы
         FAT, но сейчас отметим, что обращение к начальному кластеру явля-
         ется одним из наиболее важных первых шагов в восстановлении стер-
         тых или разрушенных файлов.

                                   Размер файла

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


                           Элементы каталога "." и ".."

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

                                                         Таблица 11-4
                   Содержимое элементов каталога  "."   и  ".."

         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                         "."  (Текущий) каталог
         ДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Поле         ¦           Содержимое поля
         ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Имя/тип      ¦ Первый байт = шестнадцатиричное значение "2Е",
                       ¦ остальная часть = 00
          Атрибут      ¦ Только состояние каталога (шестнадцатиричное
                       ¦ значение 10)
          Время        ¦ Время создания
          Дата         ¦ Дата создания
          Начальный    ¦ Номер кластера текущего каталога
          кластер      ¦
          Размер       ¦ Пробел (размер задается в элементе родитель-
                       ¦ ского каталога для этого каталога)
         --------------------------------------------------------------


                                      - 11-38 -
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                         ".."  (Родительский) каталог
         ДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Поле         ¦            Содержимое поля
         ДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Имя/тип      ¦ Первый и второй байты = шестнадцатиричное
                       ¦ значение "2Е", остальная часть = 00
          Атрибут      ¦ Только состояние каталога (шестнадцатиричное
                       ¦ значение 10)
          Время        ¦ Время создания
          Дата         ¦ Дата создания
          Начальный    ¦ Номер кластера родительского каталога только
          кластер      ¦ тогда, когда родительским является не корневой
                       ¦ каталог
          Размер       ¦ Пробел
         ДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                      Cектора таблицы размещения файла (FAT)

              Сектора, содержащие  таблицу FAT,  используются операционной
         системой MS-DOS для определения местоположения  на  диске  каждой
         части каждого файла.  В отличие от некоторых операционных систем,
         которые всегда располагают  файлы  последовательно  и  используют
         сектора  непрерывно,  операционная система MS-DOS может размещать
         файлы и части одного файла произвольным образом. Система, которая
         всегда располагает файлы последовательно,  более просто может от-
         слеживать местоположение файлов,  а,  следовательно,  может обра-
         щаться к файлам более быстро.  Для восстановления файла, располо-
         женного  непрерывно,  нам  нужно знать только адреса его начала и
         конца. Все данные в этом промежутке являются данными файла.
              Несмотря на  сказанное,  непрерывное размещение файлов менее
         эффективно,  когда например,  файл,  расположенный между  другими
         файлами,  удаляется и его место должен занять больший по размеру.
         И  если  освободившегося после удаления файла места  недостаточно
         для размещения нового файла,  для этого нового файла должно выде-
         ляться достаточное непрерывное пространство после  последнего  из
         уже размещенных на диске файлов.  Если на диске уже нет достаточ-
         ного места для помещения файла,  диск считается полным. Это может
         вызывать  реальные  трудности  при использовании гибких дисков со
         сравнительно малой емкостью памяти, потому что большое количество
         свободной памяти тратится впустую.  Операционная система MS-DOS и
         сходные с ней микрокомпьютерные операционные системы, располагаю-
         щиеся на жестком диске,  были так разработаны,  что позволяют ис-
         пользование произвольного размещения,  а также  последовательного
         или  непрерывного  размещения.  Информация в секторах таблицы FAT
         позволяет операционной системе MS-DOS выполнять это.
              Когда  диск  первый  раз форматируется операционной системой
         MS-DOS и несколько файлов копируются на этот диск, информация,по-
         мещенная в секторах таблицы FAT, используется операционной систе-
         мой MS-DOS для определения адреса каждой части файла. Обращение к
         файлам  идет через таблицу FAT в единицах "кластер".  Файл всегда
         занимает,  по крайней мере,  один кластер, а , если он достаточно
         велик,  занимает несколько кластеров.  Кластер в действительности
         является частью распределения и состоит из одного  сектора данных
         на односторонних гибких дисках и двух секторов данных на двухсто-
         ронних гибких дисках.  Некоторые жесткие диски используют класте-
         ры,  каждый  из  которых состоит из восьми секторов.  Вся область
         хранения данных на диске (за исключением области  для  данных  по

                                      - 11-39 -
         начальной загрузке таблицы FAT. На рисунках 11-5 и 11-6 показано,
         как  одно- и двухсторонние  4-трековые гибкие диски с 8-секторным
         и 9-секторным форматами отображаются в виде кластеров. На рисунке
         11-7  показано  отображение 80-трекового гибкого диска повышенной
         плотности записи.

          Формат расположения восьми        Формат расположения девяти
              секторов на треке                 секторов на треке
         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬ ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         ¦ \    Трек                    ¦ ¦ \    Трек                    ¦
         ¦   \                          ¦ ¦   \                          ¦
         ¦     \  0         1  2 ... 39 ¦ ¦     \  0         1  2 ... 39 ¦
         ¦CекторЪДДДДДДДДДДДДДДДДДДДДДДДґ ¦CекторЪДДДДДДДДДДДДДДДДДДДДДДДґ
         ¦   1  ¦ Начальная 3 11        ¦ ¦   1  ¦ Начальная 2 11        ¦
         ¦      ¦ загрузка       ...  . ¦ ¦      ¦ загрузка       ...  . ¦
         ¦   2  ¦ FAT 1     4 12 ...  . ¦ ¦   2  ¦ FAT 1     3 12 ...  . ¦
         ¦   3  ¦ FAT 2     5 13 ... 309¦ ¦   3  ¦ FAT 1     4 13 ... 346¦
         ¦   4  ¦ Каталог   6 14 ... 310¦ ¦   4  ¦ FAT 2     5 14 ... 347¦
         ¦   5  ¦ Каталог   7 15 ... 311¦ ¦   5  ¦ FAT 2     6 15 ... 348¦
         ¦   6  ¦ Каталог   8 16 ... 312¦ ¦   6  ¦ Каталог   7 16 ... 349¦
         ¦   7  ¦ Каталог   9  . ... 313¦ ¦   7  ¦ Каталог   8 17 ... 350¦
         ¦   8  ¦ 2        10  . ... 314¦ ¦   8  ¦ Каталог   9  . ... 351¦
         АДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДЩ ¦   9  ¦ Каталог  10  . ... 352¦
                                          АДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДЩ

             Рис. 11-5. Число  кластеров на односторонних 40-трековых
                                   гибких дисках

              Отметим, что на предыдущих рисунках сектора таблицы FAT были
         пронумерованы либо номером 1,  либо номером 2. Они обозначены так
         потому, что разработчики файловой операционной системы MS-DOS ре-
         зервировали в два раза больше секторов таблицы FAT,  чем действи-
         тельно требуется для отображения диска.  Возможной причиной такой
         разработки было желание предусмотреть пространство для расширения
         таблицы FAT с увеличением емкости гибких дисков. В версиях опера-
         ционной системы MS-DOS вплоть до 3.3,  однако, дополнительный на-
         бор секторов используется для размещения точной копии первого на-
         бора секторов таблицы FAT.  При наличии избыточных наборов таблиц
         FAT можно быть спокойным за них, если вдруг по какой-либо причине
         будет  разрушен первый набор.  Восстановление разрушенной таблицы
         FAT может быть очень трудным делом. При восстановлении файла, од-
         нако, вы обычно обязаны обращаться только к первой таблице FAT.
              Каждый кластер на диске имеет  соответствующий  ему  элемент
         таблицы FAT.  Элемент таблицы FAT,  который соответствует первому
         кластеру файла,  содержит номер следующего кластера занятого  тем
         же файлом. Если посмотреть на элемент таблицы FAT соответствующий
         этому "следующему" кластеру, мы обнаружим, что либо был достигнут
         конец файла, либо элемент содержит номер еще одного кластера, за-
         нятого файлом. Таким образом, элементы таблицы FAT в действитель-
         ности содержат указатели как кластеры,  занятые файлом,  так и на
         последующие элементы таблицы FAT,  которые соответствуют дополни-
         тельным кластерам,  занятым тем же файлом.  Поскольку общее число
         элементов  в секторах таблицы FAT превышает общее число кластеров
         на  диске весь диск легко может быть отображен даже тогда,  когда
         максимально использована емкость его памяти.


                                      - 11-40 -
                              Формат расположения восьми
                                   секторов на треке
                        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
                        ¦       \   Трек                      ¦
                        ¦         \                           ¦
                        ¦           \   0         1  2 ... 39 ¦
                        ¦      Cектор ЪДДДДДДДДДДДДДДДДДДДДДДДґ
                        ¦         1   ¦ Начальная 5 13        ¦
                        ¦             ¦ загрузка       ...  . ¦
                        ¦         2   ¦ FAT 1                 ¦
                        ¦         3   ¦ FAT 2     6 14 ...  . ¦
                        ¦ Сторона 4   ¦ Каталог               ¦
                        ¦    0    5   ¦ Каталог   7 15 ... 311¦
                        ¦         6   ¦ Каталог               ¦
                        ¦         7   ¦ Каталог   8 16 ... 312¦
                        ¦ ________8   ¦ Каталог               ¦
                        ¦         1   ¦ Каталог   9 16 ... 313¦
             Начало     ¦         2   ¦ Каталог               ¦
             секторов ДДДДДДДДДД> 3   ¦      2   10 18 ... 314¦
             данных     ¦         4   ¦                       ¦
                        ¦Сторона  5   ¦      3   11 19 ... 315¦
                        ¦   1     6   ¦                       ¦
                        ¦         7   ¦      4   12  . ... 316¦  Конец
                        ¦         8   ¦              .        ¦
                        АДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДЩ

                            Формат расположения девяти
                                секторов на треке
                        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
                        ¦       \   Трек                      ¦
                        ¦         \                           ¦
                        ¦           \   0         1  2 ... 39 ¦
                        ¦      Cектор ЪДДДДДДДДДДДДДДДДДДДДДДДґ
                        ¦         1   ¦ Начальная 5 14        ¦
                        ¦             ¦ загрузка       ...  . ¦
                        ¦         2   ¦ FAT 1                 ¦
                        ¦         3   ¦ FAT 1     6 15 ...  . ¦
                        ¦ Сторона 4   ¦ FAT 2                 ¦
                        ¦    0    5   ¦ FAT 2     7 16 ... 349¦
                        ¦         6   ¦ Каталог               ¦
                        ¦         7   ¦ Каталог   8 17 ... 350¦
                        ¦         8   ¦ Каталог               ¦
                        ¦ ________9   ¦ Каталог   9 18 ... 351¦
                        ¦         1   ¦ Каталог               ¦
                        ¦         2   ¦ Каталог  10 19 ... 352¦
             Начало     ¦         3   ¦ Каталог               ¦
             секторов ДДДДДДДДДД> 4   ¦      2   11 20 ... 353¦
             данных     ¦         5   ¦                       ¦
                        ¦ Сторона 6   ¦      3   12  . ... 354¦
                        ¦    1    7   ¦                       ¦   Конец
                        ¦         8   ¦      4   13  . ... 355¦
                        ¦         9   ¦                       ¦
                        АДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДЩ

             Рис.  11-6. Число кластеров на двухсторонних 40-трековых
                                   гибких дисках

                                      - 11-41 -
              На рисунках 11-5, 11-6 и 11-7 нумерация кластеров начинается
         с  цифры 2.  Это делается потому,  что кластеры нумеруются также,
         как элементы таблицы FAT (что позволяет вести их быстрый  поиск в
         таблице FAT ), а элементы 0 и 1 таблицы FAT используются для дру-
         гих целей.  Для собирания зарезервированных элементов таблицы FAT
         кластеры нумеруются,  начиная с числа 2 и продолжаются до послед-
         него элемента таблицы FAT минус 1.  Поскольку  секторы  начальной
         загрузки,  таблицы  FAT  и  каталога  не имеют номеров кластеров,
         кластер N2 содержит первый сектор (секторы) данных на диске. При-
         мер на рисунке 11-8 демонстрирует то, как можно вести обращение к
         элементам таблицы FAT.

                            Формат расположения пятнадцати
                                  секторов на треке
                        ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
                        ¦       \   Трек                             ¦
                        ¦         \                                  ¦
                        ¦           \   0         1    2   ...    79 ¦
                        ¦      Cектор ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                        ¦         1   ¦ Начальная 3   33   ...     . ¦
                        ¦             ¦ загрузка      34             ¦
                        ¦         2   ¦ FAT 1     4   35   ...     . ¦
                        ¦         3   ¦ FAT 1     5   36         1166¦
                        ¦         4   ¦ FAT 1     6   37   ...   1167¦
                        ¦         5   ¦ FAT 1     7   38         1168¦
                        ¦         6   ¦ FAT 1     8   39   ...   1169¦
                        ¦ Сторона 7   ¦ FAT 1     9   40         1170¦
                        ¦    0    8   ¦ FAT 1    10    .   ...   1171¦
                        ¦         9   ¦ FAT 2    11    .         1172¦
                        ¦        10   ¦ FAT 2    12    .   ...   1173¦
                        ¦        11   ¦ FAT 2    13    .         1174¦
                        ¦        12   ¦ FAT 2    14    .   ...   1175¦
                        ¦        13   ¦ FAT 2    15    .         1176¦
                        ¦        14   ¦ FAT 2    16    .         1177¦
                        ¦ _______15   ¦ FAT 2    17    .   ...   1178¦
                        ¦         1   ¦ Каталог  18    .         1179¦
                        ¦         2   ¦ Каталог  19    .   ...   1180¦
                        ¦         3   ¦ Каталог  20    .         1181¦
                        ¦         4   ¦ Каталог  21    .   ...   1182¦
                        ¦         5   ¦ Каталог  22    .         1183¦
                        ¦         6   ¦ Каталог  23    .   ...   1184¦
                        ¦         7   ¦ Каталог  24    .         1185¦
                        ¦ Сторона 8   ¦ Каталог  25    .   ...   1186¦
                        ¦    1    9   ¦ Каталог  26    .         1187¦
                        ¦        10   ¦ Каталог  27    .   ...   1188¦
                        ¦        11   ¦ Каталог  28    .         1189¦
                        ¦        12   ¦ Каталог  29    .   ...   1190¦
                        ¦        13   ¦ Каталог  30    .         1191¦
             Начало     ¦        14   ¦ Каталог  31    .   ...   1192¦
             секторов ДДДДДДДДДД>15   ¦ 2        32    .         1193¦
             данных     АДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

             Рис.  11-7.  Число  кластеров на двухсторонних 80-треко-
                                 вых гибких дисках



                                      - 11-42 -
                        Декодирование элементов таблицы FAT

              Значение в элементе 0 таблицы FAT всегда указывает на формат
         диска.  Элемент 1 всегда установлен в значение (F)FFF, чтобы выс-
         тупать в роли преграды или заполнителя между элементом 0  и  эле-
         ментом 2.  В таблице 11-5 представлены разные значения по каждому
         формату,  поддерживаемому операционной системой MS-DOS  версий  с
         1.0 по 3.3.
              Все последующие элементы таблицы FAT используются для  отоб-
         ражения диска.  Каждый из этих элементов содержит один из четырех
         типов информации:
              - следующий номер кластера в файле;
              - маркер конца файла;
              - неиспользованный кластер;
              - кластер,  помеченный, как зарезервированный, или испорчен-
                ный.
              В таблице 11-6 перечислены значения,  которые могут присутс-
         твовать в элементах таблицы FAT.

                                      - 43 -
         Элемент каталога для файла 1.   Элемент каталога для файла 2.
         Начальный номер кластера ука-   Начальный номер кластера ука-
         зывает на элемент 2 таблицы     зывает на элемент 5 таблицы
         FAT (первая часть файла нахо-   FAT (первая часть файла нахо-
         дится в кластере 2)             дится в кластере 5)
             ЪДДДД¦ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
             ¦    ¦    ЪДДДДДДД¬
             ¦    ¦   0¦  FFD  ¦ Двухсторонний  гибкий диск с 9
             ¦    ¦    АДДДДДДДЩ секторами на треке
             ¦    ¦    ЪДДДДДДД¬
             ¦    ¦   1¦  FFF  ¦  (Заполнитель)
             ¦    ¦    АДДДДДДДЩ
             ¦    ¦    ЪДДДДДДД¬
             ¦    АДД>2¦  003  ¦  Следующая часть файла 1 указываетДД¬
             ¦         АДДДДДДДЩ  на элемент/кластер 3               ¦
             ¦    ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

             ¦    ¦

             ¦    ¦    ЪДДДДДДД¬
             ¦    АДД>3¦  004  ¦  Следующая часть файла 1 указываетДД¬
             ¦         АДДДДДДДЩ  на элемент/кластер 4               ¦
             ¦    ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
             ¦    ¦    ЪДДДДДДД¬
             ¦    АДД>4¦  008  ¦  Следующая часть файла 1 указываетДДДД¬
             ¦         АДДДДДДДЩ  на элемент/кластер 8                 ¦
             ¦         ЪДДДДДДД¬                                       ¦
             АДДДДДДД>5¦  006  ¦  Следующая часть файла 2 указываетДД¬ ¦
                       АДДДДДДДЩ  на элемент/кластер  6              ¦ ¦
                  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ¦
                  ¦    ЪДДДДДДД¬                                       ¦
                  АДД>6¦  007  ¦  Следующая часть файла 2 указываетДД¬ ¦
                       АДДДДДДДЩ  на элемент/кластер  7              ¦ ¦
                  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ¦
                  ¦    ЪДДДДДДД¬                                       ¦
                  АДД>7¦  FFF  ¦  Конец файла 2                        ¦
                       АДДДДДДДЩ                                       ¦
                  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                  ¦    ЪДДДДДДД¬
                  АДД>8¦  009  ¦  Следующая часть файла 1 указываетДД¬
                       АДДДДДДДЩ  на элемент/кластер 9               ¦
                  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                  ¦    ЪДДДДДДД¬
                  АДД>9¦  010  ¦  Следующая часть файла 1 указываетДД¬
                       АДДДДДДДЩ  на элемент/кластер 10              ¦
                  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                  ¦    ЪДДДДДДД¬
                  АД>10¦  FFF  ¦  Конец файла 1
                       АДДДДДДДЩ
                       ЪДДДДДДД¬
               11 - 13 ¦  000  ¦  Не используется - распределяется,
                       АДДДДДДДЩ     как свободная память

                           Рис. 11-8. Пример таблицы FAT

                                      - 11-44 -


                                                         Таблица 11-5
                   Значения типа диска в элементе 0 таблицы FAT
         ДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         В элементе 0    ¦             Тип диска и формат
         (шестнадцати-   ¦
         ричное значение)¦
         ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         (F)FF0          ¦3,5-дюймовая, 1,44-Мегабайтная дискета (для
                         ¦версий 3.30 и выше операционной системы MS-DOS
                         ¦или
                         ¦другой неопределенный формат (для версий 3.30
                         ¦и выше операционной системы MS-DOS)
         (F)FF8          ¦Жесткий диск (жесткие диски в персональных
                         ¦компьютерах  IBM  PC, IBM PC-XT, IBM PC-AT с
                         ¦операционной системой MS-DOS  версии  2.0  и
                         ¦выше)
         (F)FF9          ¦Сменный носитель. Обычно имеют 5,25-дюймовый,
                         ¦1,2 Мбайтный формат гибких дисков повышенной
                         ¦емкости  (для  операционной  системы  MS-DOS
                         ¦версии 3.0 и выше)
                         ¦или
                         ¦3,5  -  дюймовый, 720 Мбайтный формат гибких
                         ¦дисков (для операционной системы MS-DOS вер-
                         ¦сии 3.20 и выше)
         (F)FFC          ¦Сменный носитель. Обычно используется для
                         ¦5,25- дюймовых, 180-Кбайтных  гибких  дисков
                         ¦(для операционной системы MS-DOS версии 2.00
                         ¦и выше)
         (F)FFD*         ¦Сменный  носитель. Обычно  используется  для
                         ¦5,25-  дюймовых,  360-Кбайтных гибких дисков
                         ¦(для операционной системы MS-DOS версии 2.00
                         ¦и выше)
                         ¦или
                         ¦8-дюймовых  501-Кбайтных  (двухсторонних   с
                         ¦одинарной плотностью записи) гибких дисков
         (F)FFE*         ¦Сменный носитель. Обычно используется для
                         ¦5,25-дюймовых,  160-Кбайтных гибких дисков
                         ¦(для операционной системы MS-DOS версии 1.00
                         ¦и выше)
                         ¦или
                         ¦8-дюймовых  250-Кбайтных  (односторонних   с
                         ¦одинарной плотностью записи) гибких дисков
                         ¦или
                         ¦8-дюймовых 1,232-Мегабайтных (двухсторонних
                         ¦с удвоенной плотностью записи) гибких дисков
         (F)FFF          ¦Сменный носитель. Обычно используется для
                         ¦5,25-дюймовых,  320-Кбайтных гибких дисков

                                      - 11-45 -
                         ¦(для операционной системы MS-DOS версии 1.10
                         ¦и выше)
         ДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

              Примечание. Некоторые  реализации  операционной  системы
              ----------  MS-DOS поддерживают работу 8-дюймовых дисков
              с программной разметкой. Несмотря  на  то,  что  обычные
              драйверы  устройств должны быть написаны для конкретного
              типа дисководов 8-дюймовых дисков, используемых в систе-
              ме, "родовая" операционная система  MS-DOS  поддерживает
              четыре  формата 8-дюймовых с одинарной плотностью записи
              (128 байтов в секторе), один двухсторонний формат с оди-
              нарной плотностью записи, и один двухсторонний формат  с
              удвоенной  плотностью  записи  (1024  байтов в секторе).
              Единственная разница между двумя односторонними формата-
              ми одинарной плотности заключается в том, что один имеет
              один зарезервированный сектор под запись начальной  заг-
              рузки, а второй имеет четыре таких сектора. Значение оп-
              ределения  формата  диска  в  элементе 0 таблицы FAT для
              формата с одинарной плотностью записи с одним зарезерви-
              рованным сектором и для формата с двойной плотностью за-
              писи одинаково и равняется  шестнадцатиричному  значению
              FFE.  Для  формата с одинарной плотностью записи и с че-
              тырьмя зарезервированными секторами таким значением  бу-
              дет  значение  FFD. Использование такого же значения для
              5,25-дюймовых гибких дисков не является проблемой, пото-
              му что операционная система MS-DOS через  свои  драйверы
              устройств   узнает,  когда  она  обращается к 8-дюймовым
              дискам. Для того,  однако,  чтобы  операционная  система
              MS-DOS могла отличать 8-дюймовые форматы от других, ког-
              да  она встречает значение FFE в элементе 0 таблицы FAT,
              она сначала считывает диск, предполагая,  что  он  имеет
              одинарную  плотность  записи, а потом пытается прочитать
              адресную отметку одинарной плотности в  первом  секторе.
              Если не происходит ошибок операционная система продолжа-
              ет чтение диска, уже зная, что он имеет формат одинарной
              плотности. Если при чтении происходит ошибка, операцион-
              ная система MS-DOS предполагает, что диск был отформати-
              рован  с удвоенной плотностью записи. Тогда операционная
              система возвращается к началу считывания диска,уже наст-
              роенная на чтение диска в формате с  двойной  плотностью
              записи. Если ваша система имеет дисководы для 8-дюймовых
              гибких  дисков,  в  руководстве  по операционной системе
              MS-DOS, приложенном к вашей конкретной реализации опера-
              ционной системы MS-DOS,должна иметься необходимая техни-
              ческая информация о форматах 8-дюймовых дисков.


              Как видно из таблиц 11-5 и 11-6,  все элементы  таблицы  FAT
         содержат либо трехцифровые, либо четырехцифровые  шестнадцатирич-
         ные номера. Это значит, что элемент таблицы FAT содержит либо 12-
         битовое, либо 16-битовое значение. Все диски, содержащие 4085 или
         меньше кластеров (большинство гибких дисков и других сменных  но-
         сителей) используют 12-битовые элементы таблицы FAT,  в то время,
         как диски, имеющие более 4085 кластеров (большинство жестких дис-
         ков  и  некоторые  сменные  диски) используют 16-битовые элементы
         таблицы FAT.

                                      - 11-46 -

                                                         Таблица 11-6
                   Значения  элементов таблицы FAT, управляющие
                                размещением файлов
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Шестнадцатиричное ¦
         значение элемента ¦               Значение
         таблицы FAT       ¦
         ДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         (0)000            ¦Кластер не используется и доступен для
                           ¦размещения нового файла
         с (F)FF0 до       ¦Зарезервированный кластер (недоступен для
         (F)FF6            ¦обычного хранения файлов)
         с (F)FF7          ¦Кластер помечен, как испорченный,
                           ¦операционной системой MS-DOS и не использу-
                           ¦ется хранения файлов.
         с (F)FF8 до       ¦Последний кластер, занятый файлом
         (F)FFF            ¦
         (X)XXX            ¦Любое другое значение указывает номер клас-
                           ¦тера в цепи, определяющей порядок  размещения
                           ¦файла
         ДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

              Почему существует два разных формата таблицы FAT?  До  того,
         как  была введена поддержка жестких дисков под управлением опера-
         ционной системы MS-DOS (версия 2.0 операционной  системы MS-DOS),
         разработчики операционной системы MS-DOS старались минимизировать
         размер памяти,  необходимый для размещения таблицы FAT. Поскольку
         максимальный размер кластеров для гибких дисков меньше числа 4085
         (2847 кластеров на 1,44 Мегабайтных 3,5-дюймовых дисках),  диско-
         вое  пространство будет тратиться напрасно,  если будут использо-
         ваться 16-битовые элементы таблицы FAT, а максимальное количество
         кластеров,  которое может быть отображено при 8-битовых элементах
         будет равняться 255,  что не отвечает  требованиям  использования
         памяти.  Поэтому для того,  чтобы декодировать значения элементов
         таблицы FAT,  сначала нужно определить общее количество кластеров
         на диске.
                    Обработка 12-битовых элементов таблицы FAT

              Схема операционной системы MS-DOS размещения чисел,  имеющих
         1,5 байта в длину, в элементах таблицы FAT на гибких дисках может
         показаться странной.  Но надо  знать,  что  операционная  система
         MS-DOS  была  спроектирована  с целью быстрого декодирования этих
         байтов. Способ, которым операционная система MS-DOS хранит инфор-
         мацию  в таблице FAT,  заключается в зашифровке элементов таблицы
         FAT парами,  в которых два 1,5-байтовых элемента объединены в не-
         большую 3-байтовую пару.  Если мы хотим определить номер кластера
         в элементе 2 таблицы FAT, мы также должны посмотреть на элемент 3
         таблицы FAT. Если мы хотим посмотреть на номер кластера в элемен-
         те 3 таблицы FAT,  мы должны вернуться назад и посмотреть на эле-
         мент 2 таблицы FAT.  Элементы 4 и 5 таблицы FAT будут тоже сдвое-
         ны,  как и элементы 6 и 7,  8 и 9 и так далее.  На  рисунке  11-9
         показано, как происходит кодирование номеров двух кластеров в па-
         ру элементов таблицы FAT при просмотре отладчиком  DEBUG  номеров
         так, как они представлены.
              На рисунке 11-10 показано,  как происходит кодирование номе-
         ров двух кластеров из пары элементов таблицы FAT.

                                      - 11-47 -
              Если из номера кластера извлекается только три цифры, почему
         же  вторая по старшинству цифра номера первого кластера перестав-
         ляется на место младшей цифры второго кластера?  Схема перестано-
         вок работает быстрее, когда машина сама декодирует байты и извле-
         кает информацию.  Цифры  появляются  уже  в  переставленном  виде
         только тогда,  когда пользователь читает таблицу FAT через отлад-
         чик DEBUG.
              Для декодирования  информации в 12-битовых элементах таблицы
         FAT на бумаге или внутри программы нужно  пользоваться  представ-
         ленной ниже последовательностью действий:
         1. Умножить номер элемента таблицы FAT или кластера на 1,5 байтов
            (сначала умножайте число на 3, а потом делите результат на 2).
         2. Используйте результат в качестве смещения в таблицу FAT,  ука-
            зывающий на элемент,  отражающий только  что  использовавшийся
            кластер.  Этот элемент содержит номер следующего кластера, за-
            нимаемого тем же файлом.
         3. Загрузите слово (2-байтовое число),  расположенное с этим сме-
            щением, в регистр.
         4. Теперь  в  регистре  находятся четыре шестнадцатиричные цифры.
            Поскольку нам нужно только три цифры для трехзначного элемента
            таблицы  FAT,  следует определить,  является ли номер элемента
            таблицы FAT четным или нечетным числом.
                             Не
                         используется
                         /          \
                  ЪДДДД/ДДДДДДДД¬     \ДДДДДДДДДДДДДД¬
         Первый   ¦  /          ¦     ¦ \            ¦ Второй
         номер    ¦ 0  1  2  3  ¦     ¦  0  4  5  6  ¦ номер
         кластера ¦    ¦  ¦  ¦  ¦     ¦     ¦  ¦  ¦  ¦ кластера
                  АДДДД†ДД†ДД†ДДЩ     АДДДДД†ДД†ДД†ДДЩ
                       АДД†ДД†ДДДДДДДДДДД¬  ¦  ¦  ¦
                          ¦  ¦  ЪДДДДДДДД†ДД†ДД†ДДЩ
                        ЪД†ДД†ДД†ДДДВДДДД†ДД†ДД†Д¬
             Первый     ¦        ¦         ¦Второй
             элемент    ¦ 2  3  6   ¦    1  4  5 ¦элемент
             таблицы FATАДДДДДДДДДДДБДДДДДДДДДДДДЩтаблицы FAT
                                      
                   Трехбайтовая пара элементов таблицы FAT
             Рис. 11-9. Кодирование  номеров  двух  кластеров  в пару
                         12-битовых элементов таблицы FAT

                        ЪДДДДДДДДДД¬     ЪДДДДДДДДДДД¬
             Первый     ¦          ¦     ¦           ¦ Второй
             элемент    ¦ 0  2  3  ¦     ¦  0  0  0  ¦ элемент
             таблицы FAT¦ ¦  ¦  ¦  ¦     ¦  ¦  ¦  ¦  ¦ таблицы FAT
                        АД†ДД†ДД†ДДЩ     АДД†ДД†ДД†ДДЩ
                          ¦  ¦  АДДДДДДДДДДД†ДД†ДД†ДД¬
                       ЪДД†ДД¦ДДДДДДДДДДДДДДЩ  ¦  ¦  ¦
                  ЪДДДД†ДД†ДД†ДД¬        ЪДДДДД†ДД†ДД†ДД¬
         Первый   ¦          ¦        ¦           ¦ Второй
         номер    ¦ 0  0  0  2  ¦        ¦  0  0  0  3  ¦ номер
         кластера ¦  \          ¦        ¦ /            ¦ кластера
                  АДДДД\ДДДДДДДДЩ        /ДДДДДДДДДДДДДДЩ
                         \             /
                         Не используется
                Рис. 11-10. Декодирование пары 12-битовых элементов
                         таблицы FAT в два номера кластера

                                      - 11-48 -

         5. Если номер элемента представлен четным числом, в регистре сле-
            дует сохранять три младшие цифры путем выполнения операции ло-
            гического сложения "И" со смещением 0FFF.  Если номер элемента
            представлен  нечетным  числом,  следует  сохранить три старшие
            цифры путем сдвига регистра вправо  на  четыре  бита  командой
            SHR.
         6. Если результирующие три цифры представляют собой число  от FF8
            до FFF,  это значит,  что вы достигли конца файла. В противном
            случае эти три цифры представляют собой номер следующего клас-
            тера, занятого тем же файлом.


                    Обработка 16-битовых элементов таблицы FAT

              Работа с номерами кластеров и элементами таблицы FAT на дис-
         ках,  которые используют 126-битовые элементы таблицы FAT, значи-
         тельно облегчается по сравнению с работой, в которой используются
         12-битовые элементы таблицы FAT,  потому что все элементы таблицы
         FAT здесь выровнены на границу слова,  то  есть,  каждый  элемент
         таблицы FAT может считываться или записываться,  как целое полное
         слово.  При этом не нужно заботиться о соседних элементах таблицы
         FAT.
              На рисунке 11-9 показано, как происходит кодирование номеров
         двух  кластеров в пару элементов таблицы FAT при просмотре отлад-
         чиком DEBUG номеров так, как они представлены.
              На рисунке 11-10 показано,  как происходит кодирование номе-
         ров двух кластеров из пары элементов таблицы FAT.
              Для декодирования  информации в 12-битовых элементах таблицы
         FAT на бумаге или внутри программы нужно  пользоваться  представ-
         ленной ниже последовательностью действий:
              1. Получить начальный кластер файла из элемента каталога
              2. Умножить используемый номер кластера на 2 (байта;  1 сло-
                 во).
              3. Используйте  результат в качестве смещения в таблицу FAT,
                 указывающий на элемент,  отражающий только что  использо-
                 вавшийся кластер.  Этот элемент содержит номер следующего
                 кластера, занимаемого тем же файлом.
              4. Загрузите слово (2-байтовое число),  расположенное с этим
                 смещением, в регистр
              5. Если результирующие четыре цифры представляют собой число
                 от FFF8 до FFFF, это значит, что вы достигли конца файла.
                 В  противном  случае  эти четыре цифры представляют собой
                 номер следующего кластера, занятого тем же файлом.


                   Преобразование кластеров в логические сектора

              Если вы пишете программу, которая будет обращаться к области
         хранения данных на диске, вы найдете, что такие средства операци-
         онной системы MS-DOS, как прерывание "int 25h" (Абсолютное считы-

                                      - 11-49 -
         вание с диска) и  прерывание  "int  26h"  (Абсолютная  запись  на
         диск), а также программа-отладчик DEBUG, требуют, чтобы вы указы-
         вали номера логических секторов. Несмотря на то, что на иллюстра-
         циях  структур  диска (см.рисунки 11-2 и 11-3) указывается первый
         сектор диска, как сторона 0, дорожка 0, сектор 1, первый сектор в
         действительности равняется логическому сектору 0. Все последующие
         логические сектора представляют собой  последовательные  смещения
         от  0.  Таким образом,  логический сектор 1 будет представляться,
         как сторона 0,  дорожка  0, сектор 2, а логический сектор 2 будет
         представляться,  как сторона 0,  дорожка 0,  сектор 3.  Поскольку
         каждый элемент таблицы FAT, согласно результатам, полученным пос-
         ле выполнения указанных выше пяти действий,  всегда создает номер
         кластера, перечисленные ниже действия покажут вам, как нужно пре-
         образовывать номер кластера в номер логического сектора:
              1. Вычесть 2 из номера кластера.
              2. Умножить полученный результат на количество векторов, ис-
                 пользуемых в кластере, следующим образом:
                 а. Для всех  односторонних  форматов  гибких  дисков  или
                    для двухстороннего 80-трекового, 5,25-дюймового форма-
                    та  диска повышенной плотности записи произвести умно-
                    жение на 1.
                 б. Для всех двухсторонних 40-трековых, 5,25-дюймовых фор-
                    матов дисков повышенной  плотности  записи  произвести
                    умножение на 2.
                 в. Для двухсторонних 8-дюймовых гибких  дисков повышенной
                    плотности записи произвести умножение на 4.
                 г. Для жестких дисков следует использовать одно из  четы-
                    рех вышеприведенных значений или другое число, завися-
                    щее от формата диска.
              3. Добавить  результат  к  номеру логического сектора начала
                 области хранения данных.

              Применяя соответствующую последовательность действий в  пра-
         вильном порядке,  вы теперь можете перейти от элемента каталога к
         элементу таблицы FAT,  к номеру кластера,  к  номеру  логического
         сектора. А если есть такое средство, кому нужна операционная сис-
         тема MS-DOS?!   Вы и сами можете считывать файлы сектор за секто-
         ром!



                           Обзор процедур восстановления

              Если файл, находящийся на диске, был каким-то образом разру-
         шен или поврежден,  существует три основных способа его исправле-
         ния. Первый способ заключается в использовании одного из встроен-
         ных средств операционной системы MS-DOS :  программы  CHKDSK  или
         программы  RECOVER.   Эти программы могут найти и изолировать по-
         врежденные области диска для того, чтобы могли восстановить часть
         или весь разрушенный файл.
              Второй способ заключается в использовании  программы  DEBUG,
         которая  позволяет вам делать все что угодно с целью восстановле-
         ния разрушенного или стертого файла. К сожалению, программа DEBUG
         предлагает  не слишком "интеллектуальную" помощь в достижении ва-
         шей цели, и часто является просто "последней соломинкой". Похожий
         на этот способ заключается в написании вашей собственной програм-
         мы восстановления,  такой,  как программа RESCUE,  представленная

                                      - 11-50 -
         ниже в этой главе.  Информация, находящаяся в предыдущем разделе,
         по декодированию каталога диска и таблицы FAT является существен-
         но важной для написания собственных программ восстановления.
              Последний и наиболее удобный способ заключается в  использо-
         вании одной из уже имеющихся готовых программ - утилит таких, как
         "Norton-утилиты" или "Ultra-утилиты". Эти утилиты подобны ящику с
         инструментами,  и в них предусмотрены все возможности перечислен-
         ных выше способов,  включая декодирование таблиц FAT, исправление
         поврежденных файлов и восстановление стертых файлов.


                Восстановление разрушенных файлов при помощи утилит
                                 CHKDSK и RECOVER

              Понимание структуры расположения диска в операционной систе-
         ме MS-DOS может оказаться очень полезным,  когда файл  или  часть
         диска  оказывается разрушенным.  К счастью,  операционная система
         содержит несколько функций, которые не только привлекают внимание
         к поврежденным частям диска,  но позволяют вам также восстанавли-
         вать данные,  доступ к которым другими способами оказался  закры-
         тым.  Встречаясь  с проблемами размещения файлов или неисправными
         дисками, операционная система MS-DOS автоматически изолирует  по-
         врежденные части  диска,  если система пытается обратиться к ним.
         Несмотря на то,  что операционная система MS-DOS  не  обязательно
         будет точно сообщать вам о том, что произошло, вы, вероятнее все-
         го,  получите сообщение об ошибке,  указывающее на то,  что часть
         диска,  с которого вы хотели вести считывание,  недоступна.  Если
         это случается,  используйте команду DIR  для  просмотра  каталога
         диска. Если команда DIR покажет, что каталог исправен, восстанов-
         ление файла (файлов)   должно вестись более или менее прямолиней-
         но: следует использовать команду RECOVER для файла или файлов.
              Если элементы каталога отсутствуют в каталоге, следует поль-
         зоваться командой CHKDSK,  сначала без параметров.  Вероятно,  вы
         получите сообщение,  говорящее о том, что определенное количество
         кластеров утеряно с диска, что является хорошим признаком, потому
         что он указывает на то,  что вы можете снова использовать команду
         CHKDSK,  но на этот раз с параметром /F. Этот параметр заставляет
         команду CHKDSK считывать все "потерянные" кластеры и  помещать их
         в один файл.  Иногда команда CHKDSK не может восстановить все по-
         терянные данные за один проход. Нужно использовать команду CHKDSK
         столько раз, сколько необходимо до появления сообщения "Утерянные
         кластеры".  Команда CHKDSK создает новый файл каждый раз  при  ее
         использовании  для восстановления потерянных данных.  После того,
         как новый файл (файлы) создан,  скопируйте его на новый  диск,  а
         затем просмотрите только что созданный файл командой CHKDSK.
              Если восстановленные данные соответствуют  текстовым файлам,
         вы можете открыть файл текстовым редактором или текстовым процес-
         сором и отсортировать информацию в нем.  Если, однако, восстанов-
         ленные данные соответствуют файлам в нечитаемом формате (например,
         в виде объектного кода или машинного кода),  вы должны  использо-
         вать  программу  DEBUG  или какую-то другую утилиту для просмотра
         информации и ее сортировки.  В любом случае не удивляйтесь,  если
         небольшая  часть данных будет все-таки утеряна.  Часть диска,  на
         которой хранились данные,  была повреждена так сильно, что данные
         с нее не могут считываться. В большинстве случаев невосстановимые
         данные состоят из приращений по 512 или по 1024 байт, что зависит
         от  формата  диска (один 512-байтовый сектор в одном кластере для

                                      - 11-51 -
         односторонних гибких дисков или два 512-байтовый сектор  в  одном
         кластере для двухсторонних гибких дисков).  Причина ,  по которой
         операционная система MS-DOS не  может  восстановить  эти  данные,
         заключается в том, что рассматриваемый кластер (кластеры) уже за-
         изолированы в соответствующих элементах таблицы FAT и каждый  та-
         кой  элемент  содержит  значение (F)FF7,  указывающее на то,  что
         кластеры испорчены и что ни одна программа не  может использовать
         их  ни при каких обстоятельствах.  Вы могли попробовать прочитать
         эти кластеры программой DEBUG, но они могут также быть так сильно
         повреждены, что и программа DEBUG не сможет их прочитать.
              В следующем разделе говорится о том,  как следует  использо-
         вать программу DEBUG для считывания частей диска сектор за секто-
         ром.

                           Восстановление стертых файлов

              К счастью,  когда  файл был стерт при работе под управлением
         операционной системы MS-DOS, только часть элемента каталога этого
         файла меняется: первый символ имени меняется на шестнадцатиричное
         значение E5. Это значение используется  как флаг (признак). Когда
         операционная  система MS-DOS просматривает секторы каталога в по-
         исках свободного места для размещения  каталоговой  информации  о
         новом файле, система находит и использует первый элемент, начина-
         ющийся либо с шестнадцатиричного значения  E5  (элемент  каталога
         еще  не использовался).  Остальная информация в элементе каталога
         остается без  изменений. Если бы все, что нам нужно сделать,  это
         изменить значение E5 на значение первого символа  имени  стертого
         файла, процедура восстановления стертых файлов была бы очень лег-
         кой.  К сожалению,  операционная система MS-DOS значительно более
         эффективно работает при стирании информации,  помещенной в секто-
         рах таблицы FAT.  Несмотря на то, что операционная система MS-DOS
         не затрагивает информации, хранящейся в секторах данных или клас-
         терах,  занятых файлом,  система устанавливает в значение 000 все
         элементы таблицы FAT,  соответствующие этим кластерам. Операцион-
         ная система MS-DOS делает это потому, что это - единственный спо-
         соб  для системы быстро просмотреть диск в поисках пустого места,
         когда она хочет разместить новые файлы. Таким образом, наша зада-
         ча по восстановлению стертого файла чуть более сложная, чем может
         сначала показаться.  Некоторые основные правила по восстановлению
         стертых файлов описываются ниже.


                                 Основные принципы

              Существует много способов потерять файл.  Может быть, вы не-
         ловко использовали команду стирания ERASE  или  команду  удаления
         DEL. Или, может быть, произошел сбой в ваших аппаратных средствах
         или сбой питания во время сеанса редактирования.  После просмотра
         диска  вы  обнаружили,  что редактируемый вами файл не может быть
         найден.
              В общем  случае файл в действительности будет стерт (при за-
         мене первого символа имени файла в элементе каталога на  шестнад-
         цатиричное значение E5) только тогда, когда он стирается командой
         ERASE или DEL или любой другой программой,  которая выполняет  ту
         же  функцию.  Если вы теряете файл из-за неисправности аппаратных
         средств или сбоя питания, файл, вероятно, не будет в действитель-
         ности стерт.  Он просто будет потерян с диска,  если используемая

                                      - 11-52 -
         вами программа не имела достаточно времени,  чтобы  закрыть  файл
         должным  образом.  В этом случае вы можете использовать программы
         операционной системы MS-DOS восстановления диска RECOVER и CHKDSK
         для восстановления потерянных данных.

         ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
         ¦                    ПРЕДОСТЕРЕЖЕНИЕ                       ¦
         ¦     При восстановлении файлов первое, что вы должны сде- ¦
         ¦лать, это создать точную копию диска при помощи программы ¦
         ¦копирования  диска. Не  размещайте  больше новые файлы на ¦
         ¦диске  до  тех пор, пока вы не сделаете попытку восстано- ¦
         ¦вить потерянные или стертые файлы.                        ¦
         АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

              Это предупреждение очень важно,  потому что помещение  новых
         данных на диск, вероятно, затруднит процесс восстановления файлов
         или сделает его невозможным. Создавая сначала точную копию диска,
         вы избежите возможности порчи некоторых частей диска (а, следова-
         тельно,  оградите себя от постоянной потери данных) в  результате
         неправильного восстановления файла. Таким образом, если с первого
         раза что-то не будет ладиться, у вас есть в запасе исходный диск,
         с  которого вы можете скопировать данные и снова начать процедуру
         восстановления файла.
              Лучшим способом определения, был ли файл действительно стерт
         или он просто потерян является использование программы CHKDSK без
         параметров.  Если файл потерян, программа CHKDSK выводит на экран
         дисплея сообщение:  "Потерянные кластеры найдены".  Это сообщение
         выводится  на  экран, если программа CHKDSK находит разрыв в цепи
         кластеров,  описанных в таблице  FAT,  например,  если  последний
         кластер в цепи не указывает на элемент таблицы FAT, в котором со-
         держится маркер конца файла (значение от (F)FF8 до  (F)FFF. Когда
         такое случается, вы должны повторно ввести программу CHKDSK с па-
         раметром /F для восстановления всех потерянных кластеров и  поме-
         щения данных в файл, созданный программой CHKDSK.
              Именно в этом месте начальный момент  создания  копии  всего
         исходного  диска может оказаться благословенным,  потому что если
         вы имели на диске также и другие действительно стертые файлы, ко-
         манда CHKDSK /F поместит файл,  содержащий восстановленные данные
         поверх области,  содержащей стертые файлы.  Восстановление файлов
         во всех случаях должно проводиться методично и аккуратно.
              После того,  как стало  ясно,  что  файл  был  действительно
         стерт,  вам точно известны три вещи:  во-первых, элемент каталога
         остается неповрежденным,  за исключением  первого  символа  имени
         файла,   который  был  заменен  шестнадцатиричным  значением  E5.
         Во-вторых,  кластеры или секторы в области данных диска использо-
         вавшиеся  исходно  файлом,  по-прежнему  содержат  данные  файла.
         В-третьих (что весьма печально!),  каждый  из  элементов  таблицы
         FAT,  использовавшихся  исходно для отражения кластеров,  занятых
         файлом, содержит значение (0)000.
              Для восстановления стертого файла следует выполнить перечис-
         ленные ниже шаги:
              1. Вести просмотр элементов каталога до тех пор,  пока вы не
                 найдете элемент, который бы начинался с шестнадцатирично-
                 го значения E5 в байте 00. Посмотрите на остальные симво-
                 лы в имени файла в байтах с 1 по 10,   и  проверьте,  что
                 это действительно был файл,  который вы хотите восстанав-
                 ливать.

                                      - 11-53 -
              2. Посмотрите на номер начального кластера (байты  26  и
                 27). Используйте номер  начального  кластера  в  качестве
                 указателя на первый кластер в области данных диска, заня-
                 того файлом, а также на первый начально используемый эле-
                 мент таблицы FAT.
              3. Посмотрите на размер файла (байты с 28 по  31,  последние
                 четыре  байта  в  элементе каталога).  Знать размер файла
                 важно в том,  случае ,  если файл занимает  более  одного
                 кластера в области данных диска,  а особенно важно,  если
                 части файла разбросаны по разным частям диска.
              4. Определив номер кластера,  занятого началом файла,  прос-
                 мотрим содержимое этого кластера.  Поищем в кластере сим-
                 вол  кода  ASCII  "Control-Z" (шестнадцатиричное значение
                 А1).  Если вы знаете,  что файл содержит  текст  (в  коде
                 ASCII)   и   если будет обнаружен один или несколько сим-
                 волов "Control-Z",  восстанавливать файл нужно  следующим
                 образом (в противном случае продолжайте работу с шага 5):
                 а. Если был обнаружен один или несколько символов
                    "Control-Z", вы знаете,  что файл занимает только один
                    кластер.  Начинайте восстанавливать файл путем помеще-
                    ния любого числа от (F)FF8 до (F)FFF в элемент таблицы
                    FAT, соответствующий этому кластеру.
                 б. Измените  шестнадцатиричное значение E5 в элементе ка-
                    талога на любой нужный вам первый символ имени файла .
                 в. Вернувшись  на  подсказку операционной системы MS-DOS,
                    используйте команду DIR для проверки  того,  что  файл
                    появился в списке файлов диска.  Откройте файл тексто-
                    вым редактором или  текстовым  процессором  для  того,
                    чтобы убедиться,  что его содержимое осталось правиль-
                    ным.  Все! Теперь вам нужно на этом остановиться и иг-
                    норировать все следующие шаги.
              5. Если конец файла не был найден, просмотрите все следующие
                 элементы  таблицы FAT (последовательно) до тех пор,  пока
                 вы не встретите элемент,  содержащий значение 000.  Прос-
                 мотрите содержимое кластера, имеющего такой же номер, что
                 и элемент таблицы FAT.  Если содержимое  окажется  частью
                 стертого файла,  запомните номер этого кластера и продол-
                 жите просмотр других элементов таблицы FAT и  эквивалент-
                 ных им кластеров до тех пор, пока вы не сочтете, что дос-
                 тигли конца файла.  Объем просмотра зависит от нескольких
                 моментов, описанных в следующем шаге.
              6. Определите по размеру файла,  взятому из элемента катало-
                 га, сколько кластеров  должен занимать файл. Кроме этого,
                 помните,  что,  если вы восстанавливаете текстовый файл в
                 коде ASCII,  наличие в кластере символа "Control-Z"(шест-
                 надцатиричное значение A1) указывает  на  конец  файла  .
                 Следовательно, возвращайтесь обратно на шаг 5 до тех пор,
                 пока вы не достигните максимального числа кластеров, за-
                 нятых  файлом.  Отмечайте каждый номер кластера,  который
                 содержит данные, относящиеся, по вашему мнению, к стерто-
                 му  файлу.  Если  в каком-то кластере вы встретили символ
                 "Control-Z",  но еще  не  определили,  сколько  кластеров
                 должно составлять искомый файл,  будьте осторожны:  клас-
                 тер,  в котором появился символ "Control-Z",  может озна-
                 чать  конец  другого  стертого файла.  Просмотрите другие
                 последующие элементы каталога по стертым файлам и отметь-
                 те  для  себя  их начальные кластеры,  а также размеры их

                                      - 11-54 -
                 файлов.  Возможно,  что два или более стертых файла имеют
                 переплетенные пути  доступа  к  последовательностям своих
                 кластеров.
              7. После  того,  как вы обоснованно решите,  какие кластеры,
                 заняты  нужным файлом,  как они сцеплены,  а также будете
                 уверены в том, что нашли конец файла, перестройте таблицу
                 FAT.  Начиная с первого кластера, перейдите к эквивалент-
                 ному  элементу  таблицы  FAT и сохраните номер следующего
                 кластера, занятого файлом.  Затем  перейдите на  следую-
                 щий  элемент  таблицы  FAT  и  сохраните номер следующего
                 кластера.  Продолжайте выполнять эту операцию до тех пор,
                 пока  не достигнете последнего кластера.  Тогда в элемент
                 таблицы FAT вы поместите любое число от (F)FF8 до (F)FFF,
                 чтобы  отметить  конец файла.  Далее перейдите к элементу
                 каталога,  относящемуся к нашему файлу и измените  первый
                 символ  из шестнадцатиричного значение E5 в то значение в
                 коде ASCII в шестнадцатиричном виде),  которое вы  хотели
                 бы видеть в качестве первого символа имени файла.
              8. Итак,  все сделано. После возвращения на подсказку опера-
                 ционной системы MS-DOS, введите команду DIR, чтобы прове-
                 рить, что файл появился в каталоге диска. Если восстанав-
                 ливаемый  файл  является текстовым файлом,  откройте файл
                 текстовым редактором  или  текстовым  процессором,  чтобы
                 проверить его содержимое. Если это файл какого-то другого
                 типа (например,  файл,  имеющий расширение имени .СОМ или
                 .EXE), загрузите файл, как программу для проверки , рабо-
                 тает ли она правильно.

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


                 Восстановление стертых файлов аппаратным способом

              Если в вашем распоряжении имеется только  программа  отладки
         DEBUG  для восстановления файлов, и вы имеете достаточно терпения
         и времени для набора и ассемблирования описанной в этой главе про-
         граммы контроля RESCUE, данные ниже советы могут оказаться полез-
         ными для вас.

                                      - 11-55 -
              Существует четыре   функции  или  команды  программы  DEBUG,
         представляющие  для  нас интерес:  L (Загрузка), D (Отображение),
         E (Ввод), W (Запись).  После того,  как вы создали копию диска со
         стертым файлом на нем,  загрузите программу DEBUG.  При появлении
         подсказки программы DEBUG введите команду L для загрузки данных с
         той части диска, которую вы хотите просмотреть:

             L <адрес> <дисковод> <начальный сектор> <конечный сектор>

              В данном случае <адрес> представляет собой начальный адрес в
         памяти,  в который должны загружаться данные, <дисковод> означает
         номер дисковода (например, 0 для А, 1 для В, 2 для С), а <началь-
         ный сектор> и <конечный  сектор>  указывают  диапазон  логических
         секторов (только шестнадцатиричные числа),  которые вы хотите за-
         грузить. Для загрузки содержимого всех секторов каталога на  дву-
         стороннюю 40-трековую 5,25-дюймовую дискету, отформатированную по
         девять секторов на трек и помещения ее 0 дисковод В, введите сле-
         дующую команду:

              А>DEBUG
              -L 0 1 5 B

              После того,  как  информация будет загружена,  вы можете ис-
         пользовать команду D для отображения на экране дисплея содержимо-
         го памяти и команду Е для изменения отдельных байтов, если нужно.
         После того,  как вы пометили нужную вам  информацию  и  произвели
         требуемые  изменения,  данные могут быть обратно записаны на диск
         при помощи команды W. Команда W использует точно такой же синтак-
         сис,  что и команда L.  Удостоверьтесь, что вы задали те же пара-
         метры, что и в команде L. Это будет вам гарантией того, что толь-
         ко правильная часть диска будет переписываться.
              Единственным моментом, когда вам нужно записывать информацию
         на диск, является тот момент, когда вы меняете первый символ име-
         ни стертого файла в относящемся к данному файлу элементе  катало-
         га,  или когда вы модифицируете содержимое элементов таблицы FAT,
         соответствующей этому файлу.  При изучении содержимого кластеров,
         занятых реальным файлом,  вам не обязательно нужно записывать ин-
         формацию обратно на диск,  если только вы не производили каких-то
         сложных работ по исправлению, которые иначе нельзя выполнить. Бо-
         лее подробную информацию по использованию программы отладки DEBUG
         и  ее команд можно найти в Руководстве пользователя по операцион-
         ной системе MS-DOS (или в аналогичном руководстве по вашей  конк-
         ретной системе).

                      Использование программы контроля RESCUE

              Программа, описанная листингом 11-4,  очень проста в исполь-
         зовании. После набора команды RESCUE задается имя стертого файла.
         Команда RESCUE требует задания только имени файла,  то есть,  за-
         данные по умолчанию дисковод и каталог,  содержащие стертый файл,
         должны быть установлены командой CHDIR (или сокращенно -  CD)  до
         запуска  программы  RESCUE.  Подлежащий  стиранию файл может быть
         файлом любого типа: обычным, скрытым, системным, доступным только
         по чтению или расположенным в подкаталоге. Если вы восстанавлива-
         ете файл, находящийся в подкаталоге, который тоже был стерт, или,
         если вы хотите проконтролировать все дерево каталога, расположен-

                                      - 11-56 -
         ное ниже стертого подкаталога,  вы  должны  сначала  восстановить
         этот подкаталог по имени, используя команду RESCUE; создать зано-
         во подкаталог в заданном по умолчанию каталоге (используя команду
         CHDIR или CD) и затем вручную восстановить стертый файл (файлы) в
         подкаталоге, используя программу RESCUE.
              Если имя  файла  найдено  в  каталоге (корневом каталоге или
         подкаталоге), будет произведена попытка проведения восстановления
         файла путем анализа и записи информации в таблицу FAT.  Если файл
         не был найден,  или,  если было обнаружено,  что он не уделен, на
         экране дисплея появится сообщение об этом.  Как пояснялось ранее,
         путь доступа,  проходящий через таблицу FAT для заданного  файла,
         может  быть  сложным.  Если программа RESCUE не сможет найти путь
         размещения файла (может быть он был  сложно  переплетен  с  путем
         доступа к другому файлу) выполнение программы завершается и ника-
         кая информация не записывается на диск.  Важным фактором в работе
         программы является то, что она не будет записывать любую информа-
         цию на диск до тех пор, пока все проблемы, связанные с файлом, не
         будут разрешены. Эта программа решает все свои задачи путем пере-
         писывания всех секторов каталога и таблицы FAT в  память,  где  и
         выполняются все модификации элементов каталога файла и его табли-
         цы FAT.  Когда все изменения были проведены, программа RESCUE за-
         писывает весь каталог и всю таблицу FAT обратно на диск.  Если во
         время анализа данных и их модифицирования  встречаются трудности,
         выполнение программы RESCUE завершается и диск остается без изме-
         нений.
              Как было отмечено ранее,  программа RESCUE была так разрабо-
         тана,  что она может работать с любым форматом диска, который со-
         ответствует стандартным соглашениям по форматированию, принятым в
         операционной системе MS-DOS.  Программа RESCUE использует не вхо-
         дящую  в  документацию по операционной системе MS-DOS функцию 32h
         (получить блок параметров диска) для получения необходимой ей ин-
         формации,  касающейся формата диска.  Эта программа также  широко
         использует  функции, содержащиеся в библиотеке поддержки языка Си
         фирмы "Майкрософт".  Если вы будете перестраивать  эту  программу
         для работы ее на другом языке или с другим компилятором, вам при-
         дется найти или самим написать заменяющие значения для этих функ-
         ций.
              Программа RESCUE намеренно сделана очень простой,  и для  ее
         понимания и для того,  чтобы она соответствовала уровню этой кни-
         ги.  Существует ряд расширений, которые вы возможно, захотите до-
         бавить в программу RESCUE,  чтобы сделать ее еще более широко ис-
         пользуемой  и   полезной.   Возможно,   вы   захотите   позволить
         пользователю  самому  указывать  в  командной  строке  по заданию
         RESCUE дисковод и каталог,  где расположен файл или вы можете за-
         хотеть разрешить универсальное задание стертых файлов с  примене-
         нием  символов "*" и "?".  Весьма полезным добавлением может ока-
         заться средство  проверки  попыток  восстановления  пользователем
         файлов,  имена которых уже существуют (что может быть сделано при
         разумном использовании имеющихся стандартных программ).
              Другая модификация,  которую вы можете захотеть  произвести,
         заключается  в  разрешении  программе  RESCUE работать с жесткими
         дисками,  которые имеют части, превышающие 32 Мегабайта, что воз-
         можно в работе под управлением операционной системы MS-DOS версии
         4.0.  Разбиения расширенного размера используют 32-битовые номера
         секторов  вместо 16-битовых номеров секторов,  использовавшихся в
         разбиениях по 32 Мегабайта и меньше. Для того, чтобы избежать ра-
         боты с 32-битовыми элементами таблицы FAT, отношение сектор/клас-

                                      - 11-57 -

         тер увеличивается в  разбиениях  расширенного  размера  так,  что
         16-битовые элементы таблицы FAT тоже  могут  по-прежнему  исполь-
         зоваться.  Однако,  максимальный размер таблицы FAT (одной ее ко-
         пии)  был  увеличен  с 64 Кбайт (для  операционной системы MS-DOS
         версии 3.3) до 128 Кбайт (для операционной системы  MS-DOS версии
         4.0).  Прикладные программы операционной системы MS-DOS экраниру-
         ются от кластера разбиения повышенного размера и от схемы отобра-
         жения секторов,  при условии, что используются только стандартные
         файловые функции операционной системы MS-DOS или абсолютные ссыл-
         ки на кластеры. Но использование абсолютными ссылками на кластеры
         прерываний 25h и 26h требует разных соглашений по обращениям меж-
         ду частями в 32 Мегабайта или меньше и частями, большими 32 Мега-
         байт. В представленных ниже листингах продемонстрированы два сог-
         лашения  в  операционной системе MS-DOS версии 4.0 для прерывания
         25h (Абсолютное чтение диска) и прерывания 26h (Абсолютная запись
         на диск).
              При некоторых изменениях программы RESCUE может быть превра-
         щена в очень могучую прикладную программу, которая может работать
         с любым типом дискового носителя, независимо от конкретной реали-
         зации и версии операционной системы MS-DOS.


             Листинг 11-2. Условия вызова операций  абсолютного  чте-
           ния/записи  на  диск  (по  прерываниям "int 25h/int 26h") для
           разделений диска в 32 Мегабайта или меньше (Все версии опера-
                              ционной системы MS-DOS)
         ----------------------------------------------------------------
         Вход :    AL = Hомер дисковода (0=А, 1=В и т.д.)
                   CX = Количество секторов для чтения (прерывание
                        "int 25h") или для  записи  (прерывание
                        "int 26h")
                   DX =  Hачальный логический номер сектора
                   DS:BX = Aдрес передачи
         Bозврат:  Признак переноса = 0 (успешная передача) или
                                    = 1 (не успешная передача)
                                     AL = Kод ошибки
                                     AH = Tип ошибки
         Примечание: B  регистре AX возвращается значение 0207h, если
                     была сделана попытка чтения или записи части раз-
                     мером больше 32 Мегабайт

           Листинг 11-3.  Условия вызова операций абсолютного чтения/за-
        писи на диск (по прерываниям "int  25h/int  26h")  для  разделений
        диска,  больших  32  Мегабайт  (только для версии 4.0 операционной
                                  системы MS-DOS)
        -----------------------------------------------------------------
         Вход :    AL = Hомер дисковода (0=А, 1=В и т.д.)
                   BX = Указатель на список параметров
                   CX = -1 (указывает на расширенный
                           (> 32 Мегабайт) формат)
         Bозврат:  Признак переноса = 0 (успешная передача) или
                                    = 1 (не успешная передача)
                                     AL = Kод ошибки
                                     AH = Tип ошибки
         Примечание: POP AX (код ошибки) при выходе. Коды ошибок

                                      - 11-58 -
                     такие же как указанные выше.

                      Cтруктура списка параметров:

                      rba     dd   ?  ; первый сектор (32-битовый с
                                      ; началом в 0) для чтения-записи
                      count   dw   ?  ; количество секторов для
                                      ; чтения-записи
                      buffer  dd   ?  ; буфер данных

                 Листинг 11-4. Программа контроля RESCUE
         _________________________________________________________

         /********************************************************
             FILE:   RESCUE2.C   Rescue File Utility Version 2.00

         Расширения:  Контроль  файла  в подкаталогах
                      Контроль стертого подкаталога
                      Управление  любым  типом  дискового носителя
                      операционной системы MS-DOS (гибкие диски,
                      жесткие диски, сменные кассеты)

         Компилирование компилятором "Си" "фирмы "Майкрософт":
            cl /c /Zp1 /AS /GO /Ze /Ot rescue2.c

         Редактирование редактором фирмы "Майкрософт":
            Link /Dosseg/MA/LI/CPAR:1/STACK:4096 rescue2,rescue2.exe,
                 rescue2.map,slibce;
            *********************************************************

            /*  В К Л Ю Ч Е Н Н Ы Е    Ф А Й Л Ы  */

            #include  <stdio.h>   /*для функции printf() и еще многих*/
            #include  <conio.h>   /*для getch() */
            #include <dos.h>      /*для intdos(),int86(), и так  далее*/
            #include  <malloc.h>  /*для _fmalloc ()& malloc */
            #include <string.h>   /*для memory "mem...()" и  str..."*/
            #iclude   <ctype.h>   /*для topper() and "есть...()" */
            #include  <direct.h>  /*для getcwd() */

            /*  О П Р Е Д Е Л Е Н И Е    К О Н С Т А Н Т  */

            #define FALSE     0   /* эти определения предназначены*/
            #define TRUE      1   /* для того, чтобы сделать програм-*/
            #define AND       &&  /* му более читаемой и  понятной*/
            #define OR        ¦¦
            #define EQ        ==
            #define NE        !=
            #define LE        <=
            #define ABS_READ       0x25 /* прерывание чтения диска */
            #define ABS_WRITE      0x26 /* прерывание записи на диск */
           /* Функции прерываний  "int 21h"  операционной системы DOS:*/
            #define DFUNC_RESETDSK 0x0D /* сброс  дисковода */
            #define DFUNC_GETDISK  0x19 /* получить текущий дисковод */
            #define DFUNC_GETDPB   0x32 /* получить блок дисковых */
                                        /* параметров */
            #define DFUNC_GETCD    0x47 /* получить текущий дисковод */

                                      - 11-59 -

              /* Биты атрибутов файла операционной системы DOS: */
            #define FATR_NONE      0x00 /* соответствует ANY */
            #define FATR_READ      0x01 /* только для чтения */
            #define FATR_HIDDEN    0x02 /* скрытый */
            #define FATR_SYSTEM    0x04 /* файл системы */
            #define FATR_VOLUME    0x08 /* метка тома */
            #define FATR_SUBDIR    0x10 /* подкаталог */
            #define FATR_ARCHIV    0x20 /* архивированный файл */

            #define CL_OFF      2    /* первым номером кластера */
                                     /* является число 2*/
            #define TENMB       20740L /* максимальное количество */
                                     /* секторов, поддерживаемых */
                                     /* 12-битовой таблице FAT */

            #define CHAIN_END   1    /* используется  функцией */
                                     /* "get_cluster ()" для */
                                     /*  обозначения конца файла*/
            #define FILE_END    0xfff8   /* элемент таблицы FAT для */
                                         /* конца файла */
              /* стандартная программа match() для совпадающих типов*/
            #define NO_MATCH    0   /* не совпадают*/
            #define IS_MATCH    1   /* совпадают */
            #define IS_ERASED   2   /* со стертыми файлами */
            #define IS_UNIQUE   4   /* со нестертыми файлами */

            #define DNAME_SIZE  80  /* максимальный размер имени в */
                                    /* каталоге */
            /*  ОПРЕДЕЛЕНИЕ  СТРУКТУР  И  ТИПОВ  */

            typedef unsigned int BOOL;
            typedef unsigned char BYTE;
            typedef unsigned int WORD;
            typedef unsigned long DWORD;
            typedef union {
                    BYTE far * ptr;
                    struct {
                        WORD off ;
                        WORD seg ;
                        } a;
                    } LONGPTR ;
            typedef struct dpbbuf {   /* буфер блока дисковых пара- */
                                      /* метров */
                BYTE PhysDrive;       /* номер дисковода */
                Byte DriverUnit ;     /* номер узла в пределах дис- */
                                      /* ковода */
                WORD BytesSector ;    /* количество байтов в секторе */
                BYTE SectorsCluster ; /* количество секторов в кластере
                                      /* минус 1 */
                BYTE ClusterShift;    /* сдвиг кластера */
                WORD  Reserve;        /* количество зарезервированных
                                      /* секторов */
                BYTE NumberOfFATs;    /* копии таблицы FAT */
                WORD DirEntries;      /* количество  элементов корне- */
                                      /* вого каталога */
                WORD DataSect;        /* первый сектор данных */

                                      - 11-60 -
                WORD TotClust;        /* общее количество кластеров */
                                      /* плюс 1 */
                BYTE nFATsec;         /* количество секторов  таблицы */
                                      /* FAT  (1 таблица FAT) */
                WORD DirSect;         /* номер сектора каталога */
                DWORD DevHeaderAddr;  /* адрес головки дисковода */
                BYTE MediaByte;       /* байт описателя носителя */
                BYTE DiskAccFlag;     /* признак доступа к диску */
                DWORD NextBlockAddr;  /* адрес следующего блока диска */
                } DPB;
            typedef struct dirbuf {   /* элемент каталога диска */
                char  name [8];       /* имя */
                char  ext  [3];       /* расширение */
                BYTE  attrib ;        /* атрибут */
                BYTE reserved [10];
                WORD time  ;          /* время: часы минуты- */
                                      /* -минуты секунды */
                WORD date ;           /* дата: год месяц-месяц день */
                WORD cluster ;        /* начальный кластер */
                DWORD fsize ;         /* общий размер в байтах */
                } DENTRY;

            /*  Г Л О Б А Л Ь Н Ы Е   П Е Р Е М Е Н Н Ы Е   * /

            DPB    far * DPBPtr ;       /* указатель блок параметров */
                                        /* диска (DPB) */
            WORD   ClUnit ;             /* количество секторов в */
                                        /* кластере (а также размер */
                                        /* буфера  каталога) */
            WORD   ByteClust ;          /* количество байтов в */
                                        /* кластере */
            DWORD  TotSect ;            /* общее количество секторов */
                                        /* на  диске */
            WORD   FATSize ;            /* количество байтов в таб- */
                                        /* лице FAT */
            WORD   far * FatAnchor ;    /* адрес буфера таблицы FAT */
            DENTRY near * DirAnchor ;   /* адрес буфера  каталога */
            DENTRY near * AltAnchor ;   /* адрес  буфера другого ка- */
                                        /* талога */
            char   defalt_sname [] ={"*.*"};  /* имя поиска по умол- */
                                        /* чанию */

            /*  ОПРЕДЕЛЕНИЕ ПСЕВДО-СТAHДАРТНЫХ ПОДПРОГРАММ  */

         #define diskread(d,s,c,b)   diskaccess(ABS_READ,d,s,c,b)
         #define diskwrite(d,s,c,b)   diskaccess(ABS_WRITE,d,s,c,b)

         #define sector_of(cl)   (DPBPtr->DataSect+(cl-CL_OFF)*ClUnit)
         #define cluster_of(sec) (CL_OFF+(sec-DPBPtr->DataSect)/ClUnit)
            /*  ПРЕДВАРИТЕЛЬНЫЕ ОБЪЯВЛЕНИЯ СТAHДАРТНОЙ ПОДПРОГРАММЕ  */

            WORD   get_cluster ();      /* получить значение элемента */
                                        /* кластера */
            void   put_cluster ();      /* сохранить значение в клас- */
                                        /* тере */
            BOOL   savefile ();         /* восстановить файл(ы) */
            DENTRY near * findt ();     /* просмотр буфера каталога */

                                      - 11-61 -
            DPB    far * getdpb () ;    /* получить адрес блока пара- */
                                        /* метров диска */
            void   diskaccess () ;      /* абсолютное чтение/запись */
                                        /* диска */
            BOOL   match () ;           /* соответствие определенного */
                                        /* имени  с файлом */

            /* *******************************************************
            /*
            /*           Главная   точка   входа
            /*
            /* *******************************************************

            main (argc,argv,envp)
            int argc ;
            char   * argv [] ;
            char   * envp [] ;
               }
               char   near * sspec ;       /* характеристика поиска */
               char   dname [DNAME_SIZE];  /* буфер имени каталога */
               char   * pptr ;             /* указатель на каталоговое
                                           /* имя */
               WORD   dnum ;               /* номер дисковода (исход- */
                                           /* ное значение 0) */
               WORD   snum ;               /* номер сектора каталога */
               WORD   savenum ;            /* используется для хра- */
                                           /* нения */
                                           /* значения snum */
               DENTRY near * dptr ;         /* элемент текущего каталога */
               print ("\nRESCUE, Version 2.OO\n\n");

               if (argc<2) {               /* если  параметров  нет */
                      print ("*** Имя файла не указано ***\n");
                      exit (1) ;
                      };
               sspec =argv [1];           /* файл для восстановления */
               getcwd (dname, DNAME_SIZE) ;  /* получить дисковод по */
                                             /* умолчанию и каталог */
               if (*(char *)((WORD)dname + strlen(dname) - 1) NE '\\')
                    strcat (dname, "\\");  /* имя пути доступа за- */
                                          /* канчивается знаком "\"*/
               pptr = dname+3;            /* указатель  начального */
                                          /* имени пути доступа*/
               dnum = *dname - 'A' ;      /* извлечь номер дисковода */

         /*  Определить разные глобальные значения из блока парамет- */
         /*  ров диска (DPB), включая размер таблицы FAT, количество */
         /*  байтов в кластере, общее количество секторов и т.д.     */

         DPBPtr = getdpb (dnum) ;  /* получить блок параметров диска */

          if(dnum NE DPBPtr->PhysDrive) {
             printf ("Дисковод %c: является замененным\n",(dnum+ 'A'));
             printf("Программа RESCUE будет работать только
                     на физических дисководах\n");
             exit (1);
             } ;

                                      - 11-62 -

            FATSize = DPBPtr->BytesSector * DPBPtr->nFATsec:
            CLUnit  = DPBPtr->SectorsCluster + 1;

         TotSect = (DWORD) DPBPtr->TotClust * (DWORD) ClUnit
                      + (DWORD) DPBPtr->DataSect;
            BytCLust= DPBPtr->BytesSector * CLUnit;
           /* Pаспределить память для буферов каталога и таблиц FAT */

              If (((DirAnchor=(Dentry near *) malloc(BytClust)) EQ
                              NULL) OR
                  ((AltAnchor=(DENTRY near *) malloc(BytCLust)) EQ
                              NULL) OR
                  ((FATAnchor=(WORD far *)_fmalloc(FATSize)) EQ
                               NULL)) {
                  printf ("*** Can't Allocate Working Memory ***\n");
                  exit (1);
                  } ;

            /* Cчитать в исходную таблицу FAT */

            discread (dnum,DPBPtr->Reserve,DPBPtr->nFATsec,FATAnchor);

            /* Двигаться по цепи элементов каталога в поисках  */
            /* соответствующего  имени пути доступа */

               snum = DPBPtr->Dirsect;    /* первый сектор каталога */
               while (*pptr NE '\0')      /* до тех пор, пока путь */
                                    /* доступа к каталогу ненулевой */
                  if ((dptr = findf (dnum, &snum, pptr, DirAnchor,
                    NULL,FATR_SUBDIR,IS_INIQUE)) NE NULL) {
                       snum = sector_of (dptr->cluster);
                       while ((*pptr NE '\0') AND (*pptr NE '\\'))
                           pptr++;
                       if (*pptr EQ '\\') pptr++;
                    } else {
                      printf ("*** Can't Find  Directory  %s  ***\n",
                                                               pptr);
                      exit (1);   };
              /* Ненормальное завершение, если файл, подлежащий  */
              /* восстановлению, не стерт. */

                    savenum = snum;    /* сохранить сектор каталога */
                   if (findf (dnum, &dnum, sspec, DirAnchor,
                      NULL,FATR_NONE,IS_UNIQUE) NE NULL) {
                  printf ("***%s%s не стерто ***\n",dname,sspec);
                  exit (1) ;
                   };
             /* Eсли файл стерт, подкаталог или файл затем */
             /* восстанавливает его */
                  snum = savenum;   /* восстановить сектор каталога */
                  if ((dptr = findf (dnum,&snum,sspec, DirAnchor,
                     NULL,FATR_NONE,IS_ERASED)) NE NULL){
                  if(get_cluster (dptr->cluster) NE 0)
                     printf ("Hестертый файл%s%s не может быть
                                                     восстановлен\n",

                                      - 11-63 -
                       dname, sspec);
                     else {
                   if (savefile (dnum,dptr, snum,toupper(*sspec))) {
                      if (dptr->attrib & FATR_SUBDIR)
                          print ("Подкаталог %s%s восстановлен\n,
                             dname,sspec);
                          else
                            printf ("Файл %s%s восстановлен\n",
                               dname,sspec);
                       } else {
                         printf (Неудачное восстановление %s%s\n",
                           dname,sspec);
                         diskread (dnum,DPBPtr->Reserve,
                           DPBPtr->nFARsec,FatAnchor);
                       };
                     };
                 } else {
                   printf ("Не могу найти нестертый файл %s%s\n",
                     dname,sspec);
                   };
                 };
         /* **** Найти следующий кластер в цепи ********************

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

              Если диск имеет емкость 10 Мегабайт или меньше,  то использу-
         ются 12-битовые элементы таблицы FAT (кластеры). Если диск больше,
         то используются 16-битовые элементы таблицы FAT.

         Значения кластера: (0)000 ................ свободный кластер
                            (0)001 ................ неопределен
                            (0)002 -(F)FEF......... следующий кластер
                            (F)FF0 -(F)FF6......... зарезервирован
                            (F)FF7 ................ испорченный кластер
                            (F)FF8 -(F)FFF......... конец цепи
         */

            WORD   get_cluster (clust)   /* номер кластера */
                WORD clust;
                {
                union { WORD FAR }* w;  /* указатель на таблицу FAT */
                    BYTE far * b;
                    }fatptr;
                WORD   value;            /* содержимое кластера */

                    if (TotSect > TENMB){
                    fatptr.b=(BYTE far *)
                      ((DWORD) FatAnchor + (DWORD)(clust * 2));
                    value = *fatptr.w;
                } else {
                fatptr.b= (BYTE far *)
                ((DWORD) FatAnchor + (DWORD)(clust * 3/2));
                value = *fatptr.w;

                                      - 11-64 -
         /*  Кластеры с нечетными номерами сдвигаются влево */
         /*  на 4 бита в слове */
                    if (clust & 0x01) value >>= 4;
                    value &= 0x0fff;
                    } ;
            if ((value & 0x0ff0) EQ 0xff0) return (CHAIN_END);
                    else return (value);
                    } ;

         /* ****  Сохранить значение кластера *************************
                 Эта стандартная  программа  помещает  значение  в  элемент
            кластера  в  таблице  FAT,  если диск имеет емкость 10 Мегабайт
            пользуются 12-битовые элементы  таблицы  FAT  (кластеры).  Если
            диск больше, то используются 16-битовые элементы таблицы FAT.

         */
            void put_cluster (clust,value)
                WORD clust;              /* номер кластера */
                WORD value;              /* значение нового кластера */
                {

            union { WORD FAR }* w;       /* указатель на таблицу FAT */
                BYTE far * b;
                }fatptr ;
            WORD   cur_val;          /* значение текущего кластера */

            if (TotSect > TENMB) {
                fatptr.b=(BYTE far *)
                ((DWORD) FatAnchor + (DWORD) (clust * 2));
                *fatptr.w=value;
                } else {
                fatptr.b= (BYTE far *)
                ((DWORD)FatAnchor + (DWORD)(clust * 3/2));
                cur_val = *fatptr.w;
         /*  Кластеры с нечетными номерами сдвигаются влево */
         /*  на 4 бита в слове */
            if (clust & 0x01)
                *fatptr.w = (cur_val & 0x000f) ¦ (значение << 4);
            else
                *fatptr.w = (cur_val & 0xf000) ¦ (значение & 0x0fff);
                } ;
            };

         /* *** Найти  кластер файла и сохранить каталог и
                                               таблицу FAT файла ***

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

                Эта стандартная программа предполагает, что номер  на-
            чального  кластера  файла, подлежащего восстановлению, был
            проверен, и его значение равно нулю.
         */

                                      - 11-65 -
            BOOL savefile (dnum,dptr,sect,ch)
                WORD    dnum ;                /* рабочий дисковод */
                DENTRY  near * dptr ;     /* элемент  каталога, */
                                          /*  подлежащий сохранению */
                WORD    sect ;            /* дать оглавление секторов */
                                          /* кластера */
                BYTE    ch ;          /* первый  символ  в имени файла */
                {
                DENTRY  far * writeptr ; /*указатель на буфер каталога*/
                WORD    filecls ;        /* размер файла (в кластерах) */
                WORD    last ;           /* номер последнего  кластера */
                WORD    current ;        /* номер текущего кластера */
                WORD    next ;           /* следующий кластер в цепи */
                WORD    fatsect ;        /* номер сектора таблицы FAT */
                union { WORD FAR }* w ;  /* указатель на таблицу FAT */
                    BYTE far * b ;
                    } fatptr ;

            if (dptr->attrib & FATR_SUBDIR)  /* если файл находится в */
                                             /* подкаталоге */
                filecls = 0 ;                /* дополнений не нужно */
            else
                filecls = (WORD) ((dptr->fsize + (DWORD)BytClust-1L)/
                (DWORD) BytClust) -1 ;
            current = last =dptr->cluster ;   /* первый кластер */

            /* Исправление таблицы FAT */

            while (filecls) {
                if (++current > DPBPtr->TotClust) {
                   print ("\n*** Нельзя восстановить файл ***/n");
                   return (FALSE) ;
                   } ;
                if (get_cluster(current) EQ 0) {  /* пустой  кластер */
                   put_cluster (last,current) ;   /* часть цепи */
                   last = current ;
                   filecls-- ;
                   } ;
                } ;
                put_cluster (last, FILE_END) ;         /* конец цепи */
                *(dptr->name) = ch ;     /* сохранить первый символ */

            /* Подготовка завершена - писать сектора таблицы FAT */
            /* и каталога */

                writeptr = DirFnchor ;
                fatsect = DPBPtr->Reserve ;
                disckwrite (dnum,fatsect,DPBPtr->nFATsec,FatAnchor) ;
                fatsect += DPBPtr->FATsec ;
                disckwrite (dnum,fatsect,DPBPtr->nFATsec,FatAnchor) ;
                disckwrite (dnum,sect,ClUnit,writeptr) ;
                dbos (DFUNC_RESETDSK,NULL,NULL);
                return(TRUE)) ;
                } ;

            /* **** Найти определенный  элемент в этом каталоге **** */


                                      - 11-66 -
            DENTRY near * findf (dnum,sect,pptr,dbuf,bptr,sattr,mtype)
                WORD    dnum ;         /* рабочий дисковод */
                WORD    * sect ;       /* сектор текущего каталога*/
                char    near * pptr ;  /* указатель имени пути доступа*/
                DENTRY  near * dbuf ;  /* буфер каталога */
                DENTRY  near * bptr ;  /* указатель другого буфера */
                BYTE    sattr ;        /* атрибут поиска */
                int     mtipe ;        /* требуемый тип совпадения */
                {
                int     i ;            /* счетчик циклов */
                WORD    cluster ;      /* используется для сцепления */
                DENTRY  near * dirptr ; /* указатель  буфера каталога*/
                DENTRY  far  * readptr ; /*указатель  буфера каталога*/
                DENTRY  near * dirend ;  /* адрес конца буфера */

                readptr = dbuf ;
                dirend = (DENTRY near *) ((WORD) dbuf + BitClust - 1) ;

                while (TRUE) {
                   if (bptr NE NULL) {    /* продолжить с ... */
                       dirptr =++bptr ;   /* того места, где */
                                          /* остановились */
                       bptr = NULL ;
                     } else {             /* иначе, начать с начала */
                       diskread (dnum,*sect,ClUnit,readptr) ;
                       dirptr = dbuf ;
                       } ;

                              /* Eсть ли совпадающие файлы ? */
                while (dirptr < dirend) {
                   if (((dirptr->attrib & sattr) EQ sattr) AND
                       (match (pptr,dirptr->name,mtype)))
                       return (dirptr) ;
                   dirptr++ ;
                   } ;

            /* Bсе элементы в этом кластере приведены, перейти к */
            /* другому */

            if (*sect >=DPBPtr->DataSect) {      /* подкаталог */
                  cluster = cluster_of (*sect) ; /* следующий кластер*/
                  if ((cluster = get_cluster (cluster)) LE CHAIN_END)
                        return (NULL) ;
                      else *sect = (sector_of (cluster)) ;
                   } else                       /* корневой каталог */
                   if (*sect >= DPBPtr->DIRSect) {
                       *sect += ClUnit ;        /* следующие сектора */
                   if (*sect >=DPBPtr->DATASect) return (NULL) ;
                   } else return (NULL) ;
                   } ;
                 } ;
               /* **** Получить блок параметров системы BIOS  ****** */
               /* **** для заданного дисковода ********* */

         DPB far *getdpb (dnum)  /* возврат указателя на блок DPB */

           Word    dnum ;     /* номер рабочего дисковода */

                                      - 11-67 -
           {
           union REGS inregs, outregs ;
           struct SREGS segregs ;
           LONGPTR farptr ;
           inregs.h.ah = DFUNC_GETDPB ;
           inregs.h.dl = dnum + 1 ;
           intdosx (&inregs, &outregs, &segregs) ; /*получить блок DPB*/
           if (outregs.x.cflag) {
            if (outregs.h.al EQ 0xff)
             print ("*** Дисковод %c неисправен ***\n",(dnum + 'A')) ;
                else
             printf ("*** Не могу считать параметры дисковода %c ***\n",
                     (dnum + 'A')) ;
              exit (1) ;
              } ;

            farptr.a.off = outregs.x.bx ;
            farptr.a.seg = segregs.ds ;
            return ((DPB far *) farptr.ptr ;
            } ;

         /* **** Чтение/запись на диск прямого доступа *********** */

         void diskaccess ((function, dnum, sector, count, buffer)
              BYTE function ;         /* функция прерывания */
              BYTE dnum ;             /* номер физического дисковода */
              WORD sector ;           /* номер сектора */
              WORD count ;            /* счет секторов */
              BYTE far * buffer ;     /* буфер */
              {
              union REGS inregs, outregs ;
              struct SREGS segregs ;
              LONGPTR farptr ;

              farptr.ptr = buffer ;
              inregs.h.al = dnum ;
              inregs.x.dx = sector ;
              inregs.x.cx = count ;
              inregs.x.bx = farptr.a.off ;
              segregs.ds = farptr.a.seg ;
              int86x (function,&inregs,&outregs,&segregs) ;
              if (outregs.x.cflag) {
                if (function EQ ABC_READ)
                 printf ("*** Ошибка во время считывания диска ***\n") ;
                   else
                       print ("*** Ошибка во время записи на диск ***\n") ;
                     exit (1) ;
                     } ;
                  } ;

         /* ************ Проверить имена на совпадение ********** */

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


                                      - 11-68 -
            BOOL match (sname, fname, mtype)
                 BYTE   near *sname ;   /* поиск совпадающего имени */
                 BYTE   near *fname ;     /* имя файла или каталога */
                 int    mtype ;            /* тип требуемого совпадения */
                 {
                 int    i ;                /* индекс */
                 char   near *fext ; /*расширение файла или каталога*/
                 fext = fname + 8;      /* расширение  файла */

         /*Сверка состояния файла (стерт/восстановлен) с типом поиска*/

                 if  (((*fname NE 0xe5) AND (mtype EQ IS_ERASED)) OR
                      ((*fname EQ 0xe5) AND (mtype NE IS_ERASED)))
                      return (NO_MATCH) ;

                 if  (*fname EQ 0xe5) {    /* игнорировать первый */
                     fname++ ;             /* символ стертого файла */
                     sname++;
                     } ;

                 while (fname < (fext+3)) {
                 if (*fname EQ toupper(*sname)) {
                    fname++ ;
                    sname++ ;
                  } else                   /* если имена различны, */
                    switch (*sname++) {    /* выяснить почему */
                      case '.':
                          if ((*fname EQ ' ') OR (fname EQ fext)) {
                            fname = fext ;  /* проверка расширения */
                            break ;
                            } ;                  /* иначе */
                          return (NO_MATCH) ;
                      case '\\':
                      case '\0:
                          if (*fname EQ ' ')   /* конец имени sname */

                                 return (IS_MATCH) ;
                      default:
                          return (NO_MATCH) ;
                      } ;
                   } ;
                   return (IS_MATCH) ;
                 } ;

            /* Конец файла RESCUE.C */
         ----------------------------------------------------------------

                           Использование утилит Нортона

              Утилиты Нортона очень легко использовать, особенно, когда вы
         что-то знаете о структуре дисков MS-DOS. В версиях утилит Нортона
         до 3.0 программы DL (просмотр блока и UE(восстановление) являются
         наиболее широко используемыми для восстановления файлов. Програм-
         ма просмотра диска DL просматривает сектор за сектором любой час-
         ти диска,  отображая шестнадцатиричные данные в левой части экра-
         на,  а  эквивалентные  им  значения в коде ASCII - в правой части
         экрана. Эта программа, достаточно удобная для определения формата

                                      - 11-69 -
         диска, а  также для определения типа считываемых секторов (таких,
         как секторов начальной загрузки, таблицы FAT, каталога или облас-
         ти  данных),  эта  программа отображает на экране эту информацию.
         Программа может также выводить на экран простое отображение  дис-
         ка,  подобное иллюстрациям структуры диска, представленным в этой
         главе, показывающим для чего используется каждый сектор или клас-
         тер с точки зрения трековой структуры. Она также показывает в ка-
         ких частях диска расположены файлы, а какие части пусты.
              Программа восстановления UnErase подобна программе DiskLook.
         Но  программа  UnErase  испытывает трудности,  если она встречает
         проблемы,  описанные в этой главе.  Например,  незавершенный файл
         поверх которого писалась новая информация или несколько файлов со
         сложными переплетениями.
              В некоторой степени ценность утилит Нортона зависит от того,
         как хорошо вы понимаете структуру дисков MS-DOS и от того, что вы
         знаете о секторах таблицы FAT и секторов каталогов, где они начи-
         наются  и  кончаются.  Даже если вы это хорошо знаете,  вы можете
         увидеть,  что эти программы являются хорошим обучающим материалом
         благодаря  ясному и подробному способу отображения на экране дан-
         ных дисков.  Другое преимущество заключается в том,  что средства
         защиты  встроены в программе с целью ограждения вас от каких-либо
         разрушающих диск действий.
              В версиях  3.0  утилит  Нортона  функции  этих двух программ
         DiskLook и UnErase объединены в программу NU  (Norton Utilities).
         Используемые  в  этой реализации функции были усовершенствованы и
         включают в себя более подробные описания содержимого диска вместо
         преимущественного криптографического шестнадцатиричного представ-
         ления данных.  Только утилиты Нортона версии 3.0 и выше могут ра-
         ботать с 80-трековыми, двухсторонними 5,25-дюймовыми гибкими дис-
         ками  высокой  плотности,  а  также  с  20-Мегабайтными  жесткими
         дисками в персональных компьютерах типа IBM PC-AT и совместимых с
         ними.  Ни одна из версий (вплоть до версии 3.0) не может работать
         с  8-дюймовыми  гибкими дисками,  а также они не могут работать с
         жесткими дисками, имеющими формат, отличный от тех, что использу-
         ются  в системах персональных компьютеров IBM XT и IBM AT. Только
         утилиты Нортона версии 4.0 могут работать со всеми форматами дис-
         ков,  которые соответствуют стандартным соглашениям по форматиро-
         ванию в операционной системе MS-DOS.  Кроме этого, поскольку спо-
         соб,  которым  эти  программы  отображают  информацию  на  экране
         сложен, они работают только с дисплейным оборудованием, совмести-
         мым или близко совместимым с оборудованием, использующимся в сис-
         темах "ИБМ".  Если,  однако,  вы будете использовать персональный
         компьютер  фирмы  "ИБМ" IBM PC или совместимую с ним систему,  вы
         увидите, что утилиты Нортона работают здесь эффективно и разнооб-
         разно, потому  что они очень хорошо подходят к темам, описанным в
         этой главе.


                           Использование "Ultra-утилит"

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


                                      - 11-70 -
         можно приобрести через многие каналы поставки программного  обес-
         печения.  "Ultra-утилиты"  содержат примечание для пользователя о
         том, что если приобретенные программы окажутся для вас полезными,
         вы можете  заплатить установленную сумму их создателям,  а взамен
         вы станете "зарегистрированным пользователем" и будете  в будущем
         получать  полную информацию об обновлениях имеющегося у вас прог-
         раммного обеспечения.
              На главном диске "Ultra-утилит"  находятся три программы:
         U-ZAP, U-FORMAT  и  U-FILE.  Программа  U-ZAP  похожа на программу
         просмотра диска DiskLook из утилит Нортона и предоставляет вам ши-
         рокие  возможности  по модификации содержимого любых частей диска.
         Программа U-FORMAT очень специфичная  программа,  потому  что  она
         обеспечивает  форматирование  отдельных  треков  диска.  Программа
         U-FORMAT может даже переформатировать  отдельный  трек  диска,  не
         разрушив  данные операционной системы MS-DOS,  находящиеся на нем.
         Эта возможность может оказаться очень полезной для ненадежно рабо-
         тающих  дисков во время возникновения таких серьезных трудностей с
         их форматированием,  что даже операционная система MS-DOS не может
         восстановить  данные,  к  которым  нет  доступа.  Программа U-FILE
         располагает многими возможностями отображения на экране и  модифи-
         цирования файлов на диске,  включая функцию восстановления стертых
         файлов.
              "Ultra-утилиты" являются   прекрасной  альтернативой   утилит
         Нортона,   если вы заботитесь о стоимости программных продуктов. И
         не   позволяйте   разубедить   себя   доступностью  этого  пакета:
         "Ultra-утилиты" действительно  являются  прекрасными  программами,
         разработанными   профессионалами,   которые  используют  недорогие
         способы их распространения.


                                    Заключение

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




                    Глава 12. ВОССТАНОВЛЕНИЕ ДАННЫХ, ПОТЕРЯННЫХ
                                     В ПАМЯТИ



              Восстановление  после сбоев, произошедших во время обра-
              ботки текста или редактирования текста
              Восстановление программ на языке Бейсик из памяти
              Заключение




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

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

              Вероятно, самым простым способом исследования системной  па-
         мяти  является  моделирование ситуации сбоя.  Загрузите выбранный
         вами текстовый процессор или текстовый редактор,  создайте корот-
         кий  простой текстовый файл и обычным способом вернитесь в работу
         с операционной системой MS-DOS. Сразу после этого загрузите прог-
         рамму  отладки DEBUG и,  используя команду D (отобразить на экра-
         не), начните сканирование  содержимого  памяти.  Программа  DEBUG
         всегда  в качестве начальной рассматривает точку памяти со смеще-
         нием 0100h.  Вам не нужно заботиться об установке адреса сегмента
         (программа  DEBUG по умолчанию устанавливает это значение в любом
         случае),  но отметьте   значение этого адреса на случай, если Вам
         понадобится вернуться к нему позже.
              В данном случае мы использовали программу  обработки текстов
         WordStar в персональном компьютере IBM PC. Если вы будете исполь-
         зовать другой текстовый процессор или другую персональную  систе-
         му,  не беспокойтесь.  Несмотря на то,  что никакие два текстовых
         редактора не работают с памятью совершенно одинаково  (есть  даже

                                      - 12-2-
         разница в работе разных версий одного и того же текстового редак-
         тора WordStar),  сам характер проведения загрузки программ опера-
         ционной  системы MS-DOS поможет нам в наших стараниях.  Почти все
         текстовые процессоры или текстовые  редакторы  сначала  загружают
         программу, а потом используют память, находящуюся выше программы,
         для хранения текста. Когда мы загружаем программу DEBUG  в систе-
         му, чаще всего программа DEBUG будет накладываться на часть текс-
         тового процессора или текстового редактора,  позволяя  нам  вести
         сканирование  памяти  снизу  вверх  в  поисках нашего потерянного
         текста.  Если случайно выбранный вами текстовый редактор окажется
         меньше   чем программа DEBUG (с точки зрения используемого прост-
         ранства в памяти),  некоторые данные могут оказаться  утерянными,
         но  для  файлов среднего размера большинство данных все еще будет
         находиться там, над программой DEBUG.

              Приведенные ниже примеры начинаются с создания образца текс-
         тового  файла,  потом  следует  описание содержимого памяти после
         загрузки текстового редактора WordStar и текстового файла,  а за-
         тем происходит обратный возврат в операционную систему MS-DOS.
              Загрузите текстовый редактор WordStar и  создайте показанный
         ниже файл TEST.TXT:

              xxxx1xxxx2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9x10
              xxx11xxx12xxx13xxx14xxx15xxx16xxx17xxx18xxx19x20
              xxx21xxx22xxx23xxx24xxx25xxx26xxx27xxx28xxx29x30
              xxx31xxx32xxx33xxx34xxx35xxx36xxx37xxx38xxx39x40
              xxx41xxx42xxx43xxx44xxx45xxx46xxx47xxx48xxx49x50
              xxx51xxx52xxx53xxx54xxx55xxx56xxx57xxx58xxx59x60
              xxx61xxx62xxx63xxx64xxx65xxx66xxx67xxx68xxx69x70
              xxx71xxx72xxx73xxx74xxx75xxx76xxx77xxx78xxx79x80
              xxx81xxx82xxx83xxx84xxx85xxx86xxx87xxx88xxx89x90
              xxx91xxx92xxx93xxx94xxx95xxx96xxx97xxx98xxx99100

              Содержимое файла TEST.TXT может показаться сначала несколько
         странным, но задача организации текста становится ясной, когда вы
         видите его (или его часть) в памяти. Этот файл состоит из ста пя-
         тисимвольных  или пятибайтовых слов.  Каждое слово нумеруется с 1
         до 100, что позволяет нам подсчитывать количество частей или слов
         текста,  которые мы действительно видим в памяти.   Отметим,  что
         последние "слова" в каждой строке (х10,  х20,  ...  100)  состоят
         только из трех символов.  Поскольку мы должны располагать символы
         "возврата каретки" и "подачи строки" в конце каждой  строки,  эти
         трехсимвольные слова  превращаются в пятисимвольные слова.  Отме-
         тим,  что некоторые программы текстовых процессоров  и  текстовых
         редакторов  вставляют  только  символ  возврата  каретки  клавиши
         Return (возврата) или Enter (ввода).  Такие  программы  выполняют
         функцию  подачи  на  строку автоматически,  в действительности не
         вставляя этот символ в текст.  В таких случаях следует  расширять
         последние  слова  каждой строки до четырех символов (хх10,  хх20,
         ... х100).
              Выйдем теперь  из  работы  с  текстовым редактором WordStar,
         сохранив файл при помощи команды Control-KX (или команды Control-
         KD,  затем  Х).  Сразу же после этого загрузите программу DEBUG и
         начните просмотр памяти на экране в поисках потерянного текста.
              Используйте команду D (отобразить на экране) для вывода дампа

                                      - 12-3 -
         содержимого памяти на экран и его просмотра до появления  искомого
         текста в правой стороне экрана дисплея.  Ниже показано, как выгля-
         дит  текст  образцового  файла  в  нашей  системе.  (Отметим,  что
         действительные адреса скорее всего будут другими в вашей системе).
         A> debug
         -d 7e10
68F8:7E10 00 00 00 00 00 00 00 00-B9 00 78 78 78 78 31 78  ........9.xxxx1x
68F8:7E20 78 78 78 32 78 78 78 78-33 78 78 78 78 34 78 78  xxx2xxxx3xxxx4xx
68F8:7E30 78 78 35 78 78 78 78 36-78 78 78 78 37 78 78 78  xx5xxxx6xxxx7xxx
68F8:7E40 78 38 32 78 78 78 39 78-31 30 0D 0A 78 78 78 31  x8xxxx9x10..xxx1
68F8:7E50 31 78 32 78 31 32 78 78-78 31 33 78 78 78 31 34  1xxx12xxx13xxx14
68F8:7E60 78 78 78 31 35 78 78 78-31 36 78 78 78 31 37 78  xxx15xxx16xxx17x
68F8:7E70 78 78 31 38 78 78 78 31-39 78 32 30 0D OA 78 78  xx18xxx19x20..xx
68F8:7E80 78 32 31 78 78 78 32 32-78 78 78 32 33 34 78 78  x21xxx22xxx23xxx
-d
68F8:7E90 32 34 78 78 78 32 35 78-78 78 32 36 78 78 78 32  24xxx25xxx26xxx2
68F8:7EA0 37 78 78 78 32 38 78 78-78 32 39 78 33 30 0D 0A  7xxx28xxx29x30..
68F8:7EB0 78 78 78 33 31 78 78 78-33 32 78 78 78 33 33 78  xxx31xxx32xxx33x
68F8:7EC0 78 78 33 34 78 78 78 33-35 78 78 78 33 36 78 78  xx34xxx35xxx36xx
68F8:7ED0 78 33 37 78 78 78 33 38-78 78 78 33 39 78 34 30  x37xxx38xxx39x40
68F8:7EE0 0D 0A 78 78 78 34 31 78-78 78 34 32 78 78 78 34  ..xxx41xxx42xxx4
68F8:7EF0 33 78 78 78 78 34 78 78-78 34 35 78 78 78 34 36  3xxx44xxx45xxx46
68F8:7F00 78 78 78 34 78 78 78 78-34 38 78 78 78 34 39 78  xxx47xxx48xxx49x
-d
68F8:7F10 35 30 0D 0A 78 78 78 35-31 78 78 78 35 32 78 78  50..xxx51xxx52xx
68F8:7F20 78 35 33 78 78 78 35 34-78 78 78 35 35 78 78 78  x53xxx54xxx55xxx
68F8:7F30 35 36 78 78 78 35 37 78-78 78 35 38 78 78 78 35  56xxx57xxx58xxx5
68F8:7F40 39 78 36 30 0D 0A 78 78-78 36 31 78 78 78 36 32  9x60..xxx61xxx62
68F8:7F50 78 78 78 33 78 78 78 78-36 34 78 78 78 36 35 78  xxx63xxx64xxx65x
68F8:7F60 78 78 36 36 78 78 78 36-37 78 78 78 36 38 78 78  xx66xxx67xxx68xx
68F8:7F70 78 36 39 78 37 30 0D 0A-78 78 78 37 31 78 78 78  x69x70..xxx71xxx
68F8:7F80 37 32 78 78 78 37 33 78-78 78 37 34 78 78 78 37  72xxx73xxx74xxx7
-d
68F8:7F90 35 78 78 78 37 36 78 78-78 37 37 78 78 78 37 78  5xxx76xxx77xxx78
68F8:7FA0 78 78 78 37 39 78 38 30-0D 0A 78 78 78 38 31 78  xxx79x80..xxx81x
68F8:7FB0 78 78 38 32 78 78 78 38-33 78 78 78 38 34 78 78  xx82xxx83xxx84xx
68F8:7FC0 78 38 35 78 78 78 38 36-78 78 78 38 37 78 78 78  x85xxx86xxx87xxx
68F8:7FD0 38 38 78 78 78 38 39 78-39 30 0D 0A 78 78 78 39  88xxx89x90..xxx9
68F8:7FE0 31 78 78 78 39 32 78 78-78 39 33 78 78 78 39 34  1xxx92xxx93xxx94
68F8:7FF0 78 78 78 39 35 78 78 78-39 36 78 78 78 39 37 78  xxx95xxx96xxx97x
68F8:8000 78 78 39 38 78 78 78 39-39 31 30 30 0D 0A 1A 1A  xx98xxx99100....
-d
68F8:8010 1A 1A 1A 1A 1A 1A 1A 1A-1A 1A 00 E8 EC 01 E8 C2  ...........hl.hb
68F8:8020 ......

              Запишите адрес,  в котором вы найдете текст.  В нашем  случае
         это  был  адрес  68F8:7E1 (шестнадцатеричное значение).  Продолжим
         просмотр памяти до тех пор, пока больше не будет видно подлежащего
         восстановлению текста, и запишем адрес конца этого текста (в нашем
         примере - это значение 68F8:8019).
              На предыдущем экране мы видели, что весь файл еще находится в
         памяти.  Если мы создали файл больший, чем имеем доступной памяти,
         только  часть этого файла (та,  что была подвержена редактированию
         последней) будет оставаться резидентной в памяти. Просматривая па-
         мять за пределами границ, показанными на предыдущем экране, мы об-
         наруживаем,  что в нашей системе 19449 байтов текста может  содер-
         жаться  в памяти.  Если мы можем так много байтов текста восстано-

                                      - 12-4 -
         вить из памяти,  мы может избежать огромных объемов повторного на-
         бора текста.  В предыдущем примере, однако, мы знаем, что достигли
         конца текста в адресе 8019,  поскольку это то место,  где закончи-
         лись  значения  строки  Control-Z (шестнадцатиричное значение 1А в
         коде ASCII).  Эти  значения  требуются  для  текстового  редактора
         WordStar в качестве маркеров конца файла, поэтому эти значения за-
         писываются на диск при сохранении файла.
              Ниже показано,  как  перепутанный  в  памяти текст может быть
         сохранен на диске,  пока вы  по-прежнему  работаете  в   программе
         DEBUG.

         -n test.sav
         -h 8019 7e1a
         FE33 01FF
         -r bx
         BX 0000
         :
         -r cx
         CX 0000
         :1ff
         -r
         AX = 0000 BX = 0000 CX = 01FF DX = 0000 SP = FFEE BP = 0000
         SI = 0000 DI = 0000
         DS = 68F8 ES = 68F8 SS = 68F8 CS = 68F8 IP = 0100   NV UP DI PL NZ
         NA PO NC
         68F8:0100 C9          DB       C9
         -w 7e1a
         Запись 01FF байтов
         -q
         A>dir test.sav
          Tом в дисководе А не имеет метки Kаталог тома в дисководе А:\
         TEST      SAV   522    4-09-85  11:03a
                 1 Файл(ы) 188416 байтов свободны
         A>

              Первый шаг в приведенном выше примере заключается в  указании
         имени файла, которое использует программа DEBUG при операциях счи-
         тывания и записи на диск при помощи команды  N  (имя).  Новое  имя
         файла должно использовать,  например,  имя TEST.SAV. Далее исполь-
         зуйте адрес смещения начала текста (7EA) и адрес конца (8019)  для
         вычисления того сколько байтов должно записываться на диск. Встро-
         енная в программу DEBUG команда  Н  ("Шестнадцатиричное  арифмети-
         ческое  действие")  является  полезным инструментом для вычисления
         нужного нам результата. При задании адресных значений после коман-
         ды Н убедитесь,  что вы указали конечный адрес перед начальным ад-
         ресом, потому что разность должна быть положительным целым числом.
         На  предыдущем  экране  результат в левой части представляет собой
         сумму двух шестнадцатиричных  адресных  значений.  Разность  между
         двумя  адресными  значениями (справа) представляет количество бай-
         тов,  которое мы хотим записать на диск.  Загрузите это значение в
         регистр  СХ,  готовясь к выполнению команды W (запись).  Отметьте,
         что регистр ВХ также используется вместе  с регистром СХ для  раз-
         мещения значений,  больших,  чем FFFF (в противном случае этот ре-
         гистр должен содержать нулевое значение).  Далее мы будем  записы-
         вать данные на диск, задавая начальный адрес.
              После того, как файл был сохранен и вы вернулись в операцион-
         ную систему MS-DOS,  наберите имя файла на экране, чтобы проверить

                                      - 12-5 -
         его содержимое.  Позже вы можете объединить этот  файл  с  другими
         частями  восстанавливаемого файла при использовании вашего тексто-
         вого процессора.
              Но что мы должны делать, когда не весь потерянный текст можно
         найти в памяти ОЗУ?  Текстовый редактор  WordStar,  подобно  боль-
         шинству других программ обработки текстов,  постоянно перемешивает
         текст, то занося его в память, то выводя его из памяти по мере ва-
         шей  работы с редактируемым текстом.  Если вы редактируете уже су-
         ществующий файл,  скажем, файл с именем TEST.TXT, текстовый редак-
         тор WordStar создает некий файл с именем TEST.$$$, которое исполь-
         зуется для хранения нового отредактированного текста.  После того,
         как вы закончили редактирование и сохранили результаты своей рабо-
         ты на диске,  программа переименовывает файл TEST.TXT  в  TEST.BAK
         (производя запись поверх старого файла TEST.BAK,  если таковой су-
         ществовал),  а файл TEST.$$$ никогда не будет виден в  каталоге  в
         момент возврата в операционную систему MS-DOS. Если, однако, прог-
         рамма завершится  аварийно, вы обнаружите файл с именем TEST.$$$ в
         каталоге.  Если нельзя будет найти весь текст в памяти,  используя
         программу DEBUG,  проверьте содержимое файла с расширением $$$  на
         наличие  в нем остального текста.  Если нельзя сразу же определить
         состояние вашего файла из  непосредственного  просмотра  каталога,
         возможно,  вам придется обратиться к дисковой утилите (такой,  как
         "Norton-утилита" или "Ultra_утилита",  описанные в предыдущей гла-
         ве),  которая может отображать скрытую на диске информацию прежде,
         чем  делать  это.  Однако, проверьте  состояние  диска  программой
         CHKDSK.  Это позволяет вам узнать, есть ли на диске какие-либо пе-
         ремешанные кластеры.  Если такие кластеры появились на диске после
         неудачно  проведенного  сеанса  редактирования,  часть потерянного
         текста может оказаться в  этих  потерянных  кластерах.  Вы  можете
         восстановить их, задав параметр /F в команде CHKDSK, но делать это
         нужно только после того,  как проверили содержимое  памяти  ОЗУ  и
         сохранили перепутанный текст на диске.
              Как было замечено выше, способы, которыми различные программы
         текстовой обработки и текстового редактирования используют память,
         сильно отличаются друг от друга.  Все они работают с  разными  ад-
         ресами в памяти.  Одни занимают больше места в памяти, чем другие.
         Некоторые программы требуют множества областей памяти для работы с
         текстом, которые иногда называют "буферами", что еще больше услож-
         няет работу.  Если,  однако, вы никогда раньше не пытались восста-
         навливать смешанные данные из памяти,  представленные выше примеры
         предоставят вам полезные для этого инструменты и средства.


                 Восстановление программ на языке Бейсик из памяти

              Проводили ли  вы когда-нибудь широкие и объемные работы с ка-
         кой-нибудь программой,  используя интерпретатор  с  языка  Бейсик,
         чтобы потом обнаружить,  что после тестирования программы встроен-
         ная команда "Return to MS-DOS"  (Возврат  в  операционную  систему
         MS-DOS) завершила работу интерпретатора до того,как вы успели сох-
         ранить программу на диске? Если программа короткая (в 20 строк или
         меньше),  это небольшое огорчение,  но вот если программа длинная,
         непредвиденное завершение работы интерпретатора - беда.
              Как только  мы  сможем  заняться  восстановлением потерянного
         текста из памяти, мы должны будем сразу же заняться и восстановле-
         нием "потерянных" программ на языке Бейсик,  потому что они должны
         целиком располагаться в памяти для удобства работы с ними  интерп-

                                      - 12-6 -
         ретатора.  А для тех интерпретаторов,  которые всегда имеют дело с
         нормальными текстовыми программа в коде ASCII, применяются описан-
         ные ранее технические средства по восстановлению текста из памяти.
         Но это не относится к интерпретаторам,  которые имеют дело с прог-
         раммами, работающими в "защищенном" режиме или с так называемыми,
         "лексемными" программами.  Лексемные программы с точки зрения ин-
         терпретатора  представляют  собой последовательность шестнадцати-
         ричных значений команд и абсолютных целых значений. Так как прог-
         рамма  в коде ASCII состоит из ряда двухцифровых ASCII - значений
         для каждого символа или числа, это значительно увеличивает размер
         файла.
              Интерпретаторы  с  языка  Бейсик  фирмы  "Майкрософт"  -
         - "Microsoft  BASIC"  и  фирмы  "ИБМ" - "IBM BASIC" являются самым
         распространенными примерами интерпретаторов,  работающих с лексем-
         ными программами. И хотя эти интерпретаторы могут читать программы
         в стандартном формате в коде ASCII,  по умолчанию они находятся  в
         состоянии работы с лексемными программами. Интерпретаторы преобра-
         зуют программу в коде ASCII, который затем загружается интерпрета-
         тором.  Проблема,  связанная  с попыткой восстановления потерянной
         лексемной программой на языке Бейсик в памяти,  заключается в том,
         что  в  действительности невозможно ее расшифровать при помощи ко-
         манды D (отобразить на экране) из программы, отладки DEBUG. Поэто-
         му следует пользоваться другим методом.
              В предложенном ниже примере показано,  как следует восстанав-
         ливать  программу,  используя  интерпретаторы с языка Бейсик фирмы
         "Майкрософт" и "ИБМ" в персональном компьютере IBM PC.  Для других
         интерпретаторов  языка  Бейсик  или  для других персональных машин
         требуются разновидности этой же процедуры,  но представленный ниже
         пример предлагает несколько советов по решению проблемы восстанов-
         ления программ в других персональных компьютерах.
              Сразу же после потери программы первым шагом  должна быть за-
         грузка программы DEBUG. Согласно техническому руководству  на  эту
         систему адрес сегмента на языке Бейсик (места, где начинается наша
         программа) может быть найден в позиции 0050:0010.  Используйте ко-
         манду D для вывода на экран дисплея первых двух значений этого ад-
         реса.  Эти значения меняются в зависимости от версии  операционной
         системы MS-DOS, версии интерпретатора языка Бейсик и размера памя-
         ти в вашей системе. Изучите предложенный ниже исходный текст прог-
         раммы и пояснения.

         A>debug
         -d 0050:0010 l2
         0050:0010 73 6B    - адрес сегмента на языке Бейсик
         -d 6b73:30 l2 - изучить сегмент (изменить на обратные байты)
         6B73:0030 EF 11    - это начальный адрес потерянной программы
         -f 6b73:11ee l1 ff - введите значение FF в начальный адрес  (поте-
                              рянной  программы  со смещением) - 1 (и снова
                              измените на обратные два  начальных  адресных
                              байта)
         -d 6b73:358 l2 - найти конечный  адрес  (потерянной  программы  со
                              смещением 0358)
         6B73:0358 88 12    - это конечный адрес
         -h 1288 11ee - вычислить количество байтов,  используемых програм-
                              мой (то же изменить  на  обратный  2-байтовый
                              конечный адрес)
         2476 009А - второе число представляет собой разность,  а  следова-
                              тельно - длину программы

                                      - 12-7 -
         -r cx               - загрузить длину программы в регистр СХ CX000
         -n  %test.bas       - задать характеристику файла, в которой
                              должна сохраняться программа
         -w 6b73:11ee -    записать байты, начиная с начала адреса программы
         Запись 009A байтов
         -q              - возврат в операционную систему MS-DOS
         A>

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

                                    Заключение

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



                              ЧАСТЬ IV. СОВМЕСТИМОСТЬ


                        Глава 13. РАЗЛИЧИЯ В ВЕPCИЯХ MS-DOS


              Общие рекомендации по совместимости
              Прерывания MS-DOS
              Вызов функций
              Коды ошибок
              Форматы дисков
              Управление файлами
              Операционная система MS-DOS
              Персональный компьютер фирмы "ИБМ"  IBM  PC  и  персональный
              компьютер фирмы "ИБМ" IBM PS/2
              Совместимость с другими операционными  системами
              Заключение



              Со времени  появления  первой  версии  операционной  системы
         MS-DOS в 1981 году разработка операционных систем  продолжалась в
         сторону  расширения ее возможностей по подключению новых аппарат-
         ных сред,  решения проблем исправления ошибок и общего  улучшения
         ее работы.
              Несмотря на то, что многие из этих улучшений привели к повы-
         шению производительности вычислительных средств, они одновременно
         оказались причиной нескольких осложнений,  поскольку не все новые
         функции  были совместимы со старыми версиями операционной системы
         MS-DOS.  Для того,  чтобы сохранить на будущее все  улучшения,  в
         этой  главе собрана информация,  призванная помочь вам определить
         совместимость разных версий операционной системы MS-DOS между со-
         бой.  Особенно полезна информация, содержащаяся в этой главе, для
         тех, кто занимается разработкой собственных программ на языке Ас-
         семблер.
              За исключением тех команд, которые по своей природе являются
         инструментами программистов (такие команды, как команда отладчика
         DEBUG и команда редактора связей LINK),  новые и расширенные  ко-
         манды операционной системы MS-DOS сравнительно редко используются
         программистами. Изменения, представляющие собой особенный интерес
         для программистов, включают в себя прерывания MS-DOS, вызов функ-
         ций,  коды ошибок,  форматы гибких и жестких дисков и  управление
         файлами. Понять все указанные изменения довольно легко, поскольку
         обращения к функциям присутствуют во всех  реализациях  любой  из
         версий операционной системы MS-DOS.
              Что касается некоторых других моментов (таких,  как управле-
         ние  памятью),  то общей схемы работы с ними быть не может,  пос-
         кольку они часто  меняются  в  зависимости  от  среды  аппаратных
         средств, для которой предназначена именно эта реализация операци-
         онной системы MS-DOS. Именно этот случай имеет место с персональ-
         ным компьютером фирмы "ИБМ" IBM PC и совместимыми с ним компьюте-
         рами.  Системы,  имеющие коренным образом отличные  от  указанной
         архитектуры  аппаратных средств,  имеют и другие схемы управления
         памятью,  чем в реализациях операционной системы MS-DOS.  Даже  в
         таких, казалось бы, "стандартных" механизмах, как прерывания, су-
         ществуют коренные различия в их работе.

                                      - 13-2 -
              Следовательно, при  разработке  прикладных программ програм-
         мисту необходимо быть знакомым с особенностями используемой  вер-
         сии.  Знание  различий между разными версиями персональных компь-
         ютеров  особенно  важно  в   тех   случаях,   когда   программист
         разрабатывает  программу с целью ее широкого применения на разных
         вычислительных средствах.  Следует помнить, что существуют разные
         версии  операционной системы MS-DOS,  зависящие от конкретной вы-
         числительной машины, и что существует множество вычислительных ма-
         шин с разными архитектурами аппаратных средств и разными реализа-
         циями операционной системы MS-DOS.  Простое следование  указаниям
         технического  руководства  по  операционной  системе MS-DOS может
         оказаться дезориентирующим,  если  вы  разрабатываете  программу,
         предназначенную для  работы  под  всеми реализациями операционной
         системы MS-DOS.
              Эта глава  не предназначена заменить техническое руководство
         по операционной системе MS-DOS.  Настоящая глава просто представ-
         ляет  собой  обзор различий в версиях операционной системы MS-DOS
         и, следовательно, является дополнением к техническим руководствам
         по всем версиям операционной системы MS-DOS.
              Сведения в этой главе поделены на четыре темы,  для  которых
         представлены  различия между всеми версиями (начиная с версии 1.0
         и кончая версией 4.0).  Там, где авторы сочли нужным, в эту главу
         включена  специальная техническая информация и советы, касающиеся
         предлагаемых  процедур или нежелательных действий,  зависящих  от
         характера разрабатываемой вами прикладной программы.

                        Общие рекомендации по совместимости

              Разные уровни  совместимости  программных средств могут быть
         предоставлены программисту.  В большинстве случаев наиболее жела-
         тельно  иметь полную совместимость.  Но поскольку обычно мы любим
         разрабатывать "блестящие" программы,  мы часто используем "новей-
         шие и улучшенные" функции,  имеющиеся в нашей реализации операци-
         онной системы MS-DOS (какие-нибудь  замысловатые  функции  экрана
         или  прерывания специального назначения),  забывая о последствиях
         их несовместимости с другими версиями. Выбор уровня совместимости
         часто  представляет  собой компромиссный вариант.  В тех случаях,
         когда мы намереваемся достичь полной совместимости,  нужно следо-
         вать предложенным ниже правилам.
              1. Ни при каких обстоятельствах не следует  пользоваться  ни
                 одной  из  команд прерываний (команды INT) семейства мак-
                 ропроцессоров 8086, за исключением тех, которые специаль-
                 но предназначены для выполнения прерываний в операционной
                 системе MS-DOS.
              2. Никогда  не следует записывать данные в абсолютные адреса
                 памяти,  расположенные за пределами вашей программы. Пре-
                 доставьте  операционной  системе  MS-DOS  самой управлять
                 процессом использования памяти.
              3. Никогда  не  следует пользоваться командами INT и OUT се-
                 мейства микропроцессоров 8086.
              4. Следует  избегать использования таких команд, выполняемых
                 только микропроцессорами моделей 80188,  80186,  80286  и
                 80386,  как:
                 PUSH   непосредственное (непосредственное проталкивание)
                 PUSHA  (проталкивание  всех  регистров)
                 POPA   (выталкивание всех регистров)
                 SHR>1  (сдвиг вправо при непосредственном значении боль-

                                      - 13-3 -
                        шем 1)
                 SHL>1  (сдвиг влево при непосредственном  значении большем
                        1)
                 IMUL   регистр назначения,  источник, непосредственное зна-
                        чение (умножение непосредственное целого значения со
                        знаком)
                 INS    исходная строка, порт (в строку)
                 OUTS   порт, строка назначения (из строки)
                 ENTER  (процедура ввода)
                 LEAVE  (процедура выхода)
                 BOUND  (обнаружение значений,  выходящих за пределы диапа-
                        зона)

                    Следует избегать использования команды  POP  CS,  пос-
                 кольку  она  работает правильно только в микропроцессорах
                 моделей 8088 и 8086.  Вам следует также знать о существо-
                 вании  всех  других  различий в работе разных процессоров
                 семейства 8086.
                     Следует избегать  использования всех команд микропро-
                 цессоров 80286/80386:
                  LGDT,LIDT, и LLDT (загрузка таблицы дескрипторов)
                  INSB (ввод через порт,  работающий с байтами)
                  OUTSB (вывод строки в  порт,  работающий  с  байтами)
                  ARPL (настройка требуемого привилегированного уровня
                        режим защиты)
                  CLTS (сброс  флага переключения задач - режим защиты)
                  LAR (загрузка прав доступа -  режим  защиты)
                  LMSW  (загрузка слова состояния машины - режим защиты)
                  LSL (загрузка границ сегмента - режим защиты)
                  LTR (загрузка регистра задачи - режим защиты)
                  SGDT, SIDT, и SLDT ( сохранение таблицы дескрипторов -
                        режим защиты)
                 SMSW (сохранение слова состояния машины - режим защиты)
                 STR (сохранение  регистра  задачи  - режим защиты) VERR и
                 VERW (проверка считывания или записи - режим
                        защиты)

                      Следует избегать  использования  команд,  работающих
                 только в микропроцессоре 80386:
                 MOV специальные регистры (перейти к/от специальных регис-
                     тров)
                 MOVSX   (переход с расширением на знак)
                 MOVZX   (переход с расширением на нуль)
                 OUTSW (вывод строки в порт,  работающий со словами)
                 BSF и BSR (сканирование битов)
                 BT,  BTC,  BTR,  и BTS (проверки битов)
                 CWDE (преобразование слова в расширенное двойное)
                 INSW (ввод из порта,  работающего со словами)
                 LFS, LGS, и LSS (загрузка заднего указателя)
                 POPAD (выталкивание всего содержимого в 32-битовые
                         регистры)
                 POPFD (выталкивание флага в 32-битовый регистр флагов)
                 PUSHAD (проталкивание  всех  32-битовых регистров)
                 PUSHFD (проталкивание 32-битового регистра флагов)
                 SET  условие (установка  по  условию)
                 SHLD  и SHRD (сдвиг с удвоенной точностью)


                                      - 13-4 -
              5. Если вычислительная машина,  которую вы  используете  для
                 разработки своей программы, имеет хранящиеся в памяти ПЗУ
                 рутины,  никогда не обращайтесь к ним.  Даже не пытайтесь
                 их считывать.
              6. Для обеспечения полной совместимости никогда не  пользуй-
                 тесь  системным вызовом функций,  поддерживаемым версиями
                 операционной системы MS-DOS только старше версии  1.0. Но
                 поскольку   более   ранние  версии  операционной  системы
                 MS-DOS,  чем версия 2.0 уже не поддерживаются программным
                 обеспечением  фирм  "Майкрософт" и "ИБМ",  задание версии
                 2.0,   как минимально-допустимой,  предоставит вам больше
                 гибкости и удобств.
              7. Во время работы всегда убеждайтесь в том, что информация,
                 выведенная на экран, состоит только из стандартных симво-
                 лов ASCII* (шестнадцатиричные значения от 00 до 7F).  Из-
                 бегайте использования любых других символов,  таких, нап-
                 ример, которые  содержатся в расширенном наборе  символов
                 для персональных  компьютеров фирмы "ИБМ" и совместимых с
                 ними.

              Если во  время  работы вы понимаете,  что вынуждены нарушить
         одно из первых правил памяти,  вы можете нарушить и шестое прави-
         ло, т.к. первым вашим действием будет написание драйвера устройс-
         тва,  предназначенного для работы с какой-то машиной,  которая  в
         противном случае окажется несовместимой. И поскольку устанавлива-
         емые драйверы устройств поддерживаются только версиями операцион-
         ной системы MS-DOS,   начиная с 2.0 и выше,  вам придется исполь-
         зовать  обращение  к  функциям, не  поддерживаемыми  операционной
         системой  MS-DOS версий 1.0 и 1.1.  Если вам необходимо (или если
         вы хотите) нарушить правило 7,  напишите драйвер  устройства  для
         нужной  машины  или "универсальную" программу установки,  которая
         могла бы использоваться для настройки прикладной программы на ра-
         боту с различными терминалами и мониторами.  Программа установки,
         конечно, должна следовать хотя бы правилу 7.
              Поскольку одним из решений вопроса о  несовместимости  может
         быть написание драйвера устройства,  мы обнаруживаем, что уже на-
         рушаем правило 6,  которое предлагает понятие другого уровня сов-
         местимости,  тоже требующего рассмотрения.  Во многих случаях вам
         захочется нарушать правило 6 намеренно,  потому что не все версии
         операционной системы MS-DOS предоставляют возможность пользовать-
         ся именно теми обращениями к функциям,  которые вы хотите или ко-
         торые  вам  нужно  использовать.  Например,  если ваша прикладная
         программа широко пользуется каталогами,  имеющими  структуру  де-
         ревьев, вероятно вы захотите пользоваться вызовами функций, имею-
         щими номера с 39 по 3B. В этом случае уровень совместимости будет
         ограничиваться  возможностями версий 2.0 и выше операционной сис-
         темы MS-DOS и будет исключать  возможность  использования  версий
         1.0  и 1.1.  Аналогичным образом,  если ваша программа должна ис-
         пользовать сетевые функции,  поддерживаемые операционной системой
         MS-DOS  версии 3.1,  эта программа не будет совместима с версиями
         операционной системы MS-DOS, начиная с 1.0 по 2.1.
              Никогда не  забывайте четко указывать ограничения по совмес-
         тимости для вашей программы либо в исходном коде  программы, либо
         в  документации  на  нее  (желательно в обоих местах).  Если ваша
         программа предназначается для коммерческой реализации, убедитесь,
         что  ограничения по совместимости (или отсутствие таких ограниче-
         ний) явно указны как в программном пакете, так и в рекламных бро-

                                      - 13-5 -
         шюрах.
              Если вы разрабатываете программу,  предназначенную  работать
         под любой такой версией операционной системы MS-DOS,  которая со-
         держит некоторые стандартные программы,  которые  могут  дополни-
         тельно выполняться при использовании конкретных версий операцион-
         ной системы MS-DOS, следует применять функцию 30h (получить номер
         версии  DOS)  для контроля за тем выполняются или нет эти опреде-
         ленные стандартные программы.  Несмотря на то,  что  эта  функция
         обеспечивается только версиями 2.0 и  выше  операционной  системы
         MS-DOS, она может безболезненно выполняться и под версиями 1.0  и
         1.1  до  тех пор,  пока будут соблюдаться предупредительные шаги,
         описанные в разделе "Вызов  DOS-функций"  вашего  руководства  по
         операционной системе MS-DOS.
              Для исполнения этой функции следует загрузить значение 30h в
         регистр АH.  При выполнении прерывания "int 21h"  основной  номер
         версии операционной системы помещается в регистр AL,  а номер мо-
         дификации основной версии операционной системы помещается  в  ре-
         гистр  AH.  Если регистр АL содержит 00,  вы можете предположить,
         что вы работаете с операционной системой MS-DOS,  версий 1.0  или
         1.1.  Любое  другое  число в регистре AL явно указывает номер ис-
         пользуемой версии.
              Если например,  вы используете операционную  систему  MS-DOS
         версии 2.00 в регистре Al будет содержаться число 02, а в регист-
         ре An будет содержаться число 00.  Если вы используете операцион-
         ную  систему MS-DOS  версии 3.10, в регистре AL будет содержаться
         число 03, а в регистре An будет содержаться число 10. Даже тогда,
         когда  вы не должны контролировать выполнение некоторых стандарт-
         ных программ, эта функция позволяет вам управлять отображением на
         экране дисплея "дружественного" сообщения, когда пользователь пы-
         тается запустить программу под несовместимой версией операционной
         системы MS-DOS.  Программа,  представленная листингом 13-1, может
         использоваться в вашей программе для выполнения этой функции.


             Листинг  13-1. Стандартная программа по определению вер-
                          сии операционной системы MS-DOS
         ----------------------------------------------------------------
         ;     Стандартная  программа по определению версии операцион-
         ;  ной системы MS-DOS), под управлением  которой  запускается
         ;  программа, содержащая эту стандартную программу.
         ;
         ;      Примечание:  Убедитесь, что перечисленные ниже команды
         ;  заданы либо в сегменте данных, либо в области данных  сег-
         ;  мента кодов в вашей программе.
         ;
         ;     majver  db ?  ; основной номер версии операционной
         ;                   ; системы (в шестнадцатеричном  виде)
         ;     minver  db ?  ; номер модификации основной версии
         ;                   ; операционной системы (в шестнадцатирич-
                             ; ном виде)

              getdosver     proc   near   ; если нужно, изменить версию
         ;
                  push   ax         ; сохранение регистров
                  push   bx
                  push   cx
              ;

                                      - 13-6 -
                  mov    ah,30h     ; подготовить номер функции
                  int    21h        ; выполнить вызов функции MS-DOS
              ;
                  cmp    al,0       ; проверить, используется ли версия
                                    ; до 2.0
                  jnz    dos2plus   ; если нет, значит используется
                                    ; версия 2.00 или выше
                  mov    al,1       ; основной  является версия 1.00
                  mov    ah,0       ; (поскольку мы знаем,  что  ре-
                                    ; гистр  AH, по-прежнему, сожер-
                                    ; жит номер функции (30h), мы не
                                    ; сможем выяснить номера модифи-
                                    ; кации основной версии операци-
                                    ; онной  системы.   Поэтому   мы
                                    ; предполагаем  наихудший случай
                                    ; - версия 1.00)
         ;
              dos2plus:
                  mov majver,al     ; сохранение основного номера
                                    ; версии операционной системы
                  mov minver,ah     ; сохранение номера модификации
                                    ; основной версии операционной
                                    ; системы
              ;
                  pop    cx         ; восcтановление регистров
                  pop    bx
                  pop    ax
              ;
                  ret               ; возврат
              ;
              getdosver endp
         ----------------------------------------------------------------

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

             Некоторые соображения относительно языков высокого уровня

              Если вы пишете программу на языке высокого уровня, вы должны
         быть осведомлены о характеристиках используемого вами конкретного
         компилятора или интерпретатора. Если в спецификации на данное ус-
         тройство говорится, что ваш компилятор или интерпретатор работает
         только под  определенной  версией  операционной  системы  MS-DOS,
         скомпилированная  или проинтерпретированная здесь Вами программа,
         вероятнее всего не будет работать под более ранними версиями опе-
         рационной системы.  Особенно это касается таких интерпретаторов с
         языка Бейсик,  как "Microsoft/IBM BASIC" и "GWBASIC",  потому что
         новые версии этих  интерпретаторов часто выпускаются настроенными
         на выполнение только новых версий операционной системы MS-DOS.



                                      - 13-7 -
                                 Прерывания MS-DOS

              Программные прерывания,  заданные для использования операци-
         онной системы MS-DOS, совместимы для всех версий.  Исключение со-
         ставляет прерывание  2Fl,  которое было добавлено только в версию
         3.0. В таблице 13-1 приведен перечень прерываний.
                                                         Таблица 13-1
                                 Прерывания MS-DOS
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДДД
         Номер ¦ Описание  ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ преры-    ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦ вания     ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДДД
         20    ¦Завершение ¦
               ¦программы  ¦
         ДДДДДД†ДДДДДДДДДДДґ
         21    ¦Запрос к   ¦
               ¦функции    ¦
         ДДДДДД†ДДДДДДДДДДДґ
         22    ¦Адрес      ¦
               ¦завершения ¦
         ДДДДДД†ДДДДДДДДДДДґ
         23    ¦Адрес      ¦
               ¦выхода по  ¦
               ¦Ctrl/Break ¦
         ДДДДДД†ДДДДДДДДДДДґ
         24    ¦Вектор     ¦
               ¦драйвера   ¦
               ¦критической¦
               ¦ошибки     ¦
         ДДДДДД†ДДДДДДДДДДДґ
         25    ¦Чтение     ¦                        Да
               ¦диска (в   ¦
               ¦абсолютных ¦
               ¦адресах)   ¦
         ДДДДДД†ДДДДДДДДДДДґ
         26    ¦Запись на  ¦
               ¦диск (в    ¦
               ¦абсолютных ¦
               ¦адресах)   ¦
         ДДДДДД†ДДДДДДДДДДДґ
         27    ¦Завершение ¦
               ¦с сохране- ¦
               ¦нием рези- ¦
               ¦дентности  ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         28    ¦Зарезерви- ¦
               ¦ровано     ¦ Для внутреннего использования операционной
               ¦           ¦             системы MS-DOS
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         29    ¦           ¦
         ДДДДДДґ(Зарезерви-¦               (Зарезервировано)
         2E    ¦  ровано   ¦

                                      - 13-8 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД
         Номер ¦ Описание  ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ преры-    ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦ вания     ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
         2F    ¦Мульти-    ¦                  ¦
               ¦плексное   ¦     Нет          ¦           Да
               ¦прерывание ¦                  ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         30-   ¦Зарезерви- ¦                Зарезервировано
         66    ¦ровано     ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         67    ¦Интерфейс  ¦        ¦                             ¦
               ¦системы    ¦  Нет   ¦      (см. Примечание 1)     ¦ Да
               ¦расширенной¦        ¦                             ¦
               ¦памяти     ¦        ¦                             ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         68-   ¦Зарезерви- ¦                Зарезервировано
         6F    ¦ровано     ¦
         ДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

              Примечание 1: Обращение  к  системе  расширенной  памяти
         (EMS):    как    задается    в    обеих    спецификациях:   в
         "Lotus/Intel/Microsoft" (LIM) и  в  "AST/Quadram/Ashton-Tate"
         (AQA),  происходит одинаково посредством прерывания "int 67h"
         во всех версиях операционной системы MS-DOS, начиная в версии
         2.0. И только в операционной  системе  MS-DOS  версии  4.0  и
         старше  это прерывание официально зарезервировано для обраще-
         ния к EMS. Подробнее о функциях прерывания "int 67h" системы
         EMS говорится в главе 7.
              Многие вычислительные  машины имеют прерывания,  не перечис-
         ленные в таблице 13-1.  Эти прерывания задаются для таких  специ-
         альных применений, как обращение к программам системы BIOS (базо-
         вая система ввода-вывода) или взаимодействие  с последовательными
         портами. Не следует путать эти обращения с прерываниями, заданны-
         ми для использования в операционной системе MS-DOS.  Только  опи-
         санные в "Техническом руководстве по операционной системе MS-DOS"
         прерывания являются истинными прерываниями  операционной  системы
         MS-DOS.  Для поддержания совместимости со всеми реализациями опе-
         рационной системы MS-DOS  следует  избегать  использования  любых
         прерываний,  не  являющихся  истинными  прерываниями операционной
         системы MS-DOS . Информация о недокументированных прерываниях при-
         ведена в приложении Б.

                                   Вызов функций

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

                   Выполнение вызова функций стандартным образом
              Когда на свете появилась первая версия  операционной системы
         MS-DOS,  в  ней  существовало  два способа выполнения обращений к
         функциям.  Первый рекомендованный для использования со всеми вер-
         сиями  операционной системы MS-DOS способ представлен ниже в виде
         семи последовательных действий:
              1. Сохранение  содержимого регистров AX,  BX,  CX и DХ путем
                 выталкивания их значений в стек.
              2. Помещение номера функции в регистр AH.
              3. Помещение других данных в регистры,  указанные для выпол-
                 нения заданной функции, если это нужно.
              4. Выполнение команды прерывания "int 21h".
              5. В  зависимости от выполняемой функции,  переменные данные
                 возвращаются в указанных  регистрах  для  возможности  их
                 дальнейшего считывания и использования в вашей программе.
                 Некоторые функции ничего не возвращают.
              6. Выполнение требуемой операции с использованием возвращен-
                 ных данных от только что выполненной  функции,  если  это
                 нужно.
              7. Восстановление исходного содержания регистров.

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

                 Выполнение вызова функций в режиме совместимости

              Второй способ выполнения обращения к функциям, предоставляе-
         мый операционной  системой MS-DOS для обеспечения совместимости с
         другими операционными системами,  конкретно предназначен для опе-
         рационных систем CP/M-80 и CP/M-86. Этот способ в действительнос-
         ти не обеспечивает возможность запуска программ операционной сис-
         темы CP/M под управлением операционной системы MS-DOS.  Он только
         упрощает и облегчает преобразование программ операционной системы
         CP/M в программы операционной системы MS-DOS тем, что при этом не
         всегда требуется переопределение процедуры обращения  к функциям.
         Но, однако, Вам, вероятно, придется менять многие номера функций.
         Данный  способ пригоден только для функций с номерами от 0 до 24h
         операционной системы MS-DOS.  Возможно, Вы встретитесь с труднос-
         тями при использовании регистров в некоторых обращениях к функци-
         ям,  поэтому следует избегать использования  этого  метода,  если
         только вы не собираетесь тестировать вашу программу до ее полного
         преобразования.  Операционная система MS-DOS требует, чтобы обра-
         щение  к функциям с использованием этого второго способа выполня-
         лось следующим образом:
              1. Сохранение содержимого регистров AX,  BX,  CX и DХ  путем
                 выталкивания их значений в стек.
              2. Помещение номера функции в регистр CL.  (Могут  использо-
                 ваться только номера функций, начиная с 0 и до 24h).
              3. Помещение других данных в регистры,  указанные для выпол-
                 нения заданной функции, если это нужно.
              4. Произвести внутрисегментное обращение к адресу  5  внутри
                 текущего сегмента программы.  Этот адрес содержит длинное
                 обращение  к  диспетчеру  функций  операционной   системы

                                      - 13-10 -
                 MS-DOS.
              5. В зависимости от выполняемой функции,  переменные  данные
                 возвращаются  в  указанных  регистрах  для возможности их
                 дальнейшего считывания и использования в вашей программе.
                 Некоторые функции ничего не возвращают.  Примечание:  Эта
                 процедура всегда стирает содержимое регистра AX.  Все ос-
                 тальные регистры ведут себя так же, как и при использова-
                 нии стандартной процедуры обращения к функциям.
              6. Восстановление исходного содержания регистров.

             Еще один способ (только для версий операционной  системы
                          MS-DOS, начиная с 2.00 и выше)

              В версии  2.00  операционной  системы MS-DOS был представлен
         третий способ обращения к функциям.  Этот метод также  может  ис-
         пользоваться  с более старшими версиями,  но он будет неправильно
         работать со всеми более ранними версиями. Третий способ обращения
         к функциям выполняется следующим образом:
              1. Сохранение содержимого регистров AX,  BX,  CX и DХ  путем
                 выталкивания их значений в стек.
              2. Помещение номера функции в регистр AH.
              3. Помещение других данных в регистры,  указанные для выпол-
                 нения заданной функции.
              4. Произвести  длинное  обращение  к  адресу  со  смещением
                  "шестнадцатиричное 50" в префиксе программного сегмента.
              5. В  зависимости от выполняемой функции,  переменные данные
                 возвращаются в указанных  регистрах  для  возможности  их
                 дальнейшего считывания и использования в вашей программе.
                 Некоторые функции ничего не возвращают.
              6. Восстановление исходного содержания регистров путем обра-
                 ботки выборки из стека.

              С появлением операционной системы  MS-DOS  версии  3.10  обе
         фирмы - и "Майкрософт"  и  "ИБМ" рекомендуют не пользоваться этим
         способом.  Зачем же он тогда был введен?  Одно из  возможных  его
         применений  может объяснить это (по адресу шестнадцатиричное сме-
         щение 50) в PSP (в префиксе программного сегмента) обычно  содер-
         жится  команда прерывания "int 21h".  Используя метод,  описанный
         выше, программист направлял все обращения к функциям операционной
         системы MS-DOS (исключая другие прерывания) только через один ад-
         рес.  Изменив команду,  находящуюся по адресу  "шестнадцатиричное
         смещение  50",  вы  можете перенаправить все обращения к функциям
         операционной системы MS-DOS.  Является ли в настоящее время  этот
         способ обращения к функциям уже оставленной попыткой фирмы "Майк-
         рософт" реализовать мультизадачный режим?  Точный ответ  на  этот
         вопрос знает только сама фирма "Майкрософт".


                Функции, выполняемые в разных версиях операционной
                                  системы MS-DOS

              В таблице  13-2  приведен полный список функций операционной
         системы MS-DOS,  поддерживаемый версиями с 1.0 до 3.1 . В таблице
         также указаны функции, являющиеся новыми для некоторых версий.


                                      - 13-11 -
                                                         Таблица 13-2
                        Функции операционной системы MS-DOS
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
          0    ¦Завершение ¦
               ¦программы  ¦
         ДДДДДД†ДДДДДДДДДДДґ
          1    ¦Ввод  с    ¦
               ¦клавиатуры ¦
         ДДДДДД†ДДДДДДДДДДДґ
          2    ¦Вывод на эк¦
               ¦ран дисплея¦
         ДДДДДД†ДДДДДДДДДДДґ
          3    ¦AUX-ввод   ¦                        Да
         ДДДДДД†ДДДДДДДДДДДґ
          4    ¦AUX вывод  ¦
         ДДДДДД†ДДДДДДДДДДДґ
          5    ¦Вывод  на  ¦
               ¦печатающее ¦
               ¦устройство ¦
               ¦(принтер)  ¦
         ДДДДДД†ДДДДДДДДДДДґ
          6    ¦Непосред-  ¦
               ¦ственный   ¦
               ¦ввод-вывод ¦
               ¦на консоль ¦
         ДДДДДД†ДДДДДДДДДДДґ
          7    ¦Нефильтрую-¦
               ¦щий ввод с ¦
               ¦консоли без¦
               ¦эха        ¦
         ДДДДДД†ДДДДДДДДДДДґ
          8    ¦ Ввод с    ¦
               ¦ консоли   ¦
               ¦ без эха   ¦
         ДДДДДД†ДДДДДДДДДДДґ
          9    ¦ Печать    ¦
               ¦ строки    ¦                   Да
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Буферизован¦
          A    ¦ный ввод  с¦
               ¦клавиатур  ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Проверка   ¦
          B    ¦стандартно-¦
               ¦го состоя- ¦
               ¦ния ввода  ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Очистить бу¦
          C    ¦фер клавиа-¦
               ¦туры и     ¦
               ¦ждать ввода¦
               ¦с клавиа-  ¦
               ¦туры       ¦

                                      - 13-12 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
               ¦Сброс      ¦
          D    ¦диска      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Выбор      ¦
          E    ¦диска      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Открыть    ¦
          F    ¦файл       ¦
         ДДДДДД†ДДДДДДДДДДДґ
         10    ¦Закрыть    ¦
               ¦файл       ¦
         ДДДДДД†ДДДДДДДДДДДґ
         11    ¦Поиск      ¦
               ¦первого    ¦                        Да
               ¦элемента   ¦
               ¦каталога   ¦
         ДДДДДД†ДДДДДДДДДДДґ
         12    ¦Поиск сле- ¦
               ¦дующего    ¦
               ¦элемента   ¦
               ¦каталога   ¦
         ДДДДДД†ДДДДДДДДДДДґ
         13    ¦Удалить    ¦
               ¦файл       ¦
         ДДДДДД†ДДДДДДДДДДДґ
         14    ¦Последова- ¦
               ¦тельное    ¦
               ¦считывание ¦
         ДДДДДД†ДДДДДДДДДДДґ
         15    ¦Последова- ¦
               ¦тельная    ¦
               ¦запись     ¦
         ДДДДДД†ДДДДДДДДДДДґ
         16    ¦Создать    ¦
               ¦файл       ¦
         ДДДДДД†ДДДДДДДДДДДґ                  Да
         17    ¦Переимено- ¦
               ¦вать файл  ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         18    ¦(Зарезерви-¦////////////////////////////////////////////
               ¦ровано)    ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         19    ¦ Опросить  ¦
               ¦ текущий   ¦
               ¦ диск      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Установить ¦
         1A    ¦адрес нача-¦
               ¦ла области ¦
               ¦(DTA)      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Информация ¦

                                      - 13-13 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
         1B    ¦таблицы    ¦                       Да
               ¦размещения ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Информация ¦
         1C    ¦по конкрет-¦
               ¦ному уст-  ¦
               ¦ройству    ¦
               ¦таблицы    ¦
               ¦размещения ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦(Зарезерви-¦////////////////////////////////////////////
         1D-20 ¦ровано)    ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         21    ¦Произволь- ¦
               ¦ное считы- ¦
               ¦вание      ¦
         ДДДДДД†ДДДДДДДДДДДґ
         22    ¦Произволь- ¦
               ¦ная запись ¦
         ДДДДДД†ДДДДДДДДДДДґ
         23    ¦Размер     ¦                        Да
               ¦файла      ¦
         ДДДДДД†ДДДДДДДДДДДґ
         24    ¦Установить ¦
               ¦поле запи- ¦
               ¦си прямого ¦
               ¦доступа    ¦
         ДДДДДД†ДДДДДДДДДДДґ
         25    ¦Установить ¦
               ¦вектор     ¦
               ¦прерывания ¦
         ДДДДДД†ДДДДДДДДДДДґ
         26    ¦Создать    ¦
               ¦новый про- ¦
               ¦граммный   ¦
               ¦сегмент    ¦
         ДДДДДД†ДДДДДДДДДДДґ
         27    ¦Произволь- ¦
               ¦ное считы- ¦
               ¦вание блока¦
         ДДДДДД†ДДДДДДДДДДДґ
         28    ¦Произволь- ¦
               ¦ная запись ¦
               ¦блока      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Синтакси-  ¦
         29    ¦ческий раз-¦
               ¦бор тексто-¦
               ¦вого имени ¦
               ¦файла в    ¦
               ¦FCB-формат ¦
         ДДДДДД†ДДДДДДДДДДДґ

                                      - 13-14 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
         2А    ¦Получить   ¦
               ¦системную  ¦
               ¦дату       ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Установить ¦                      Да
         2B    ¦системную  ¦
               ¦дату       ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Получить   ¦
         2C    ¦системное  ¦
               ¦время      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Установить ¦
         2D    ¦системное  ¦
               ¦время      ¦
         ДДДДДД†ДДДДДДДДДДДґ
               ¦Установить/¦
         2E    ¦/сбросить  ¦
               ¦переключа- ¦
               ¦тель       ¦
               ¦верификации¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Дать адрес ¦        ¦
         2F    ¦ (DTA)     ¦  Нет   ¦            Да
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Получение  ¦        ¦
         30    ¦номера вер-¦        ¦
               ¦сии MS-DOS ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ   Нет  ¦             Да
         31    ¦Завершиться¦        ¦
               ¦но остаться¦        ¦
               ¦резидентным¦        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         32    ¦(Зарезер-  ¦////////////////////////////////////////////
               ¦вировано)  ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         33    ¦Статус     ¦        ¦
               ¦проверки   ¦   Нет  ¦                Да
               ¦Ctrl/Break ¦        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         34    ¦(Зарезер-  ¦////////////////////////////////////////////
               ¦вировано)  ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         35    ¦Получить   ¦        ¦
               ¦вектор     ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
         36    ¦Получить   ¦        ¦
               ¦размер сво-¦  Нет   ¦                 Да
               ¦бодного про¦        ¦
               ¦странства  ¦        ¦
               ¦на диске   ¦        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД

                                      - 13-15 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Прерывание     ¦    Версия операционной системы MS-DOS
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
         37    ¦(Зарезер-  ¦////////////////////////////////////////////
               ¦вировано)  ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДВДДДД†ДДДБДДДД†ДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД
         38    ¦Инфор-¦    ¦        ¦
               ¦мация ¦    ¦        ¦
               ¦о теку¦Полу¦        ¦                Да
               ¦щей   ¦чить¦        ¦
               ¦стране¦    ¦        ¦
               ¦где усГДДДДґ        †ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦танов-¦    ¦        ¦         ¦
               ¦лено  ¦Уста¦        ¦         ¦
               ¦обору-¦но- ¦        ¦  Нет    ¦          Да
               ¦дова- ¦вить¦        ¦         ¦
               ¦ние   ¦    ¦        ¦         ¦
         ДДДДДД†ДДДДДДБДДДДґ        ГДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         39    ¦Создать    ¦        ¦
               ¦подкаталог ¦  Нет   ¦
               ¦(MKDIR)    ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Удалить    ¦        ¦
         3A    ¦подкаталог ¦        ¦
               ¦(RMDIR)    ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Изменить   ¦        ¦                    Да
         3B    ¦текущий    ¦        ¦
               ¦каталог    ¦        ¦
               ¦(CHDIR)    ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Создать    ¦        ¦
         3C    ¦файл       ¦        ¦
               ¦(CREATE)   ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Открыть    ¦        ¦
         3D    ¦файл (обыч-¦        ¦
               ¦ный на     ¦        ¦
               ¦диске)     ¦        ¦
               ГДДДДДДДДДДДґ        ГДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Открыть    ¦        ¦         ¦
               ¦сетевой    ¦        ¦   Нет   ¦          Да
               ¦файл       ¦        ¦         ¦
         ДДДДДД†ДДДДДДДДДДДґ        ГДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Закрыть    ¦        ¦
         3E    ¦файл       ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Считать из ¦        ¦
         3F    ¦файла или с¦        ¦
               ¦устройства ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Писать в   ¦        ¦
         40    ¦файл или на¦        ¦
               ¦устройство ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦

                                      - 13-16 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       41    ¦Удалить    ¦        ¦
            Прерывание     ¦    Версия операционной системы MS-DOS                   ¦файл из    ¦        ¦
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД             ¦заданного  ¦        ¦
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦                  ¦каталога   ¦  Нет   ¦                   Да
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0              ¦(UNLINK)   ¦        ¦
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦            ДДДДДД†ДДДДДДДДДДДґ        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД       42    ¦Установить ¦        ¦
               ¦указатель  ¦        ¦
               ¦чтения/за  ¦        ¦
               ¦писи файла ¦        ¦
               ¦(LSEEK)    ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
         43    ¦Изменить   ¦        ¦
               ¦атрибут    ¦        ¦
               ¦режима     ¦        ¦
               ¦файла      ¦        ¦
               ¦(CHMOD)    ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
         44    ¦Канал вза- ¦        ¦
               ¦имодей-    ¦        ¦
               ¦ствия с    ¦        ¦
               ¦драйверами ¦        ¦
               ¦устройств  ¦        ¦
               ¦(IOCTL)    ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦00 Дать ин-¦        ¦
               ¦формацию об¦        ¦
               ¦устройстве ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦01 Устано- ¦        ¦
               ¦вить инфор-¦        ¦
               ¦мацию об   ¦        ¦
               ¦устройстве ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦02 Считыва-¦        ¦
               ¦ние с по-  ¦        ¦
               ¦символьного¦  Нет   ¦                   Да
               ¦устройства ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦03 Запись  ¦        ¦
               ¦на посим-  ¦        ¦
               ¦вольное    ¦        ¦
               ¦устройство ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦04 Считыва-¦        ¦
               ¦ние с блоч-¦        ¦
               ¦ного       ¦        ¦
               ¦устройства ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦05 Запись  ¦        ¦
               ¦на блочное ¦        ¦
               ¦устройство ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦06 Получить¦        ¦
               ¦входное    ¦  Нет   ¦                   Да
               ¦состояние  ¦        ¦
               ГДДДДДДДДДДДґ        ¦
               ¦07 Получить¦        ¦
               ¦ состояние ¦        ¦

                                      - 13-17 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       41    ¦Удалить    ¦        ¦
            Прерывание     ¦    Версия операционной системы MS-DOS                   ¦файл из    ¦        ¦
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД             ¦заданного  ¦        ¦
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦                  ¦каталога   ¦  Нет   ¦                   Да
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0              ¦(UNLINK)   ¦        ¦
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦            ДДДДДД†ДДДДДДДДДДДґ        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД       42    ¦Установить ¦        ¦
               ¦ ввода     ¦        ¦
               ГДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦08 Является¦                  ¦
               ¦ли блочное ¦                  ¦
               ¦устройство ¦     Нет          ¦            Да
               ¦переадресу-¦                  ¦
               ¦емым?      ¦                  ¦
               ГДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦09 Является¦                       ¦
               ¦логическое ¦                       ¦
               ¦устройство ¦                       ¦
               ¦локальным  ¦                       ¦
               ¦или        ¦                       ¦
               ¦удаленным? ¦                       ¦
               ГДДДДДДДДДДДґ          Нет          ¦         Да
               ¦0A Управле-¦                       ¦
               ¦ние осущест¦                       ¦
               ¦вляется ло-¦                       ¦
               ¦кальным или¦                       ¦
               ¦удаленным  ¦                       ¦
               ¦образом?   ¦                       ¦
               ГДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦0B Изменить¦                  ¦
               ¦количество ¦                  ¦
               ¦попыток    ¦       Нет        ¦           Да
               ¦исправления¦                  ¦
               ¦сбоя       ¦                  ¦
               ГДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦0C Запрос  ¦                                 ¦
               ¦на получе- ¦                                 ¦
               ¦ние описа- ¦                                 ¦
               ¦теля       ¦                                 ¦
               ¦"Generic   ¦                                 ¦
               ¦IOCTL"     ¦              Нет                ¦    Да
               ¦(переключе-¦                                 ¦
               ¦ние страниц¦                                 ¦
               ¦программы) ¦                                 ¦
               ГДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦0D Запрос  ¦                            ¦
               ¦на "Generic¦                            ¦
               ¦IOCTL"     ¦                            ¦
               ¦блочного   ¦                            ¦
               ¦устройства ¦                            ¦
               ГДДДДДДДДДДДґ                            ¦
               ¦0E Получить¦            Нет             ¦       Да
               ¦логическое ¦                            ¦
               ¦устройство ¦                            ¦
               ГДДДДДДДДДДДґ                            ¦
               ¦0F Устано- ¦                            ¦
               ¦вить логи- ¦                            ¦
               ¦ческое     ¦                            ¦
               ¦устройство ¦                            ¦

                                      - 13-18 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       41    ¦Удалить    ¦        ¦
            Прерывание     ¦    Версия операционной системы MS-DOS                   ¦файл из    ¦        ¦
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД             ¦заданного  ¦        ¦
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦                  ¦каталога   ¦  Нет   ¦                   Да
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0              ¦(UNLINK)   ¦        ¦
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦            ДДДДДД†ДДДДДДДДДДДґ        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД       42    ¦Установить ¦        ¦
         45    ¦Дублировать¦        ¦
               ¦описатель  ¦        ¦
               ¦файла (DUP)¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
         46    ¦Переназна- ¦        ¦
               ¦чить описа-¦        ¦
               ¦тель файла ¦        ¦
               ¦(CDUP)     ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
         47    ¦Дать теку- ¦        ¦
               ¦щий каталог¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
         48    ¦Распреде-  ¦        ¦
               ¦лить блок  ¦        ¦
               ¦памяти     ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ  Нет   ¦                 Да
         49    ¦Освободить ¦        ¦
               ¦распреде-  ¦        ¦
               ¦ленный     ¦        ¦
               ¦блок памяти¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Модифициро-¦        ¦
         4A    ¦вать (сжать¦        ¦
               ¦или расши- ¦        ¦
               ¦рить) блоки¦        ¦
               ¦распределен¦        ¦
               ¦ной памяти ¦        ¦
               ¦(SETBLOCK) ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Загрузить  ¦        ¦
         4B    ¦или выпол--¦        ¦
               ¦ненить     ¦        ¦
               ¦программу  ¦        ¦
               ¦(ЕХЕС)     ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Завершить  ¦        ¦
         4C    ¦процесс    ¦        ¦
               ¦(EXIT)     ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Получить   ¦        ¦
         4D    ¦код завер- ¦        ¦
               ¦шения под- ¦  Нет   ¦                 Да
               ¦процесса   ¦        ¦
               ¦(WAIT)     ¦        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Найти пер- ¦        ¦
         4E    ¦вый совпав-¦        ¦
               ¦ший файл   ¦        ¦
               ¦(FIND FIRST)        ¦
         ДДДДДД†ДДДДДДДДДДДґ        ¦
               ¦Найти сле- ¦        ¦
         4F    ¦дующий сов-¦        ¦

                                      - 13-19 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       41    ¦Удалить    ¦        ¦
            Прерывание     ¦    Версия операционной системы MS-DOS                   ¦файл из    ¦        ¦
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД             ¦заданного  ¦        ¦
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦                  ¦каталога   ¦  Нет   ¦                   Да
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0              ¦(UNLINK)   ¦        ¦
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦            ДДДДДД†ДДДДДДДДДДДґ        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД       42    ¦Установить ¦        ¦
               ¦павший файл¦        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         50-   ¦(Зарезер-  ¦////////////////////////////////////////////
         -53   ¦вировано)  ¦////////////////////////////////////////////
               ¦           ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         54    ¦Получить   ¦        ¦
               ¦режим вери-¦        ¦
               ¦фикации    ¦  Нет   ¦                 Да
               ¦диска      ¦        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         55    ¦(Зарезер-  ¦////////////////////////////////////////////
               ¦вировано)  ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         56    ¦Переимено- ¦        ¦
         57    ¦вать файл, ¦        ¦
               ¦запросить/ ¦  Нет   ¦                 Да
               ¦/установить¦        ¦
               ¦дату и вре-¦        ¦
               ¦мя файла   ¦        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         58    ¦(Зарезер-  ¦////////////////////////////////////////////
               ¦вировано)  ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         59    ¦Дать расши-¦                  ¦
               ¦ренную     ¦                  ¦
               ¦информацию ¦                  ¦
               ¦об ошибке  ¦                  ¦
         ДДДДДД†ДДДДДДДДДДДґ                  ¦
               ¦Создать    ¦       Нет        ¦           Да
         5A    ¦временный  ¦                  ¦
               ¦файл       ¦                  ¦
         ДДДДДД†ДДДДДДДДДДДґ                  ¦
               ¦Создать    ¦                  ¦
         5B    ¦новый файл ¦                  ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Блокировка/¦                  ¦
         5C    ¦/освобожде-¦       Нет        ¦           Да
               ¦ние доступа¦                  ¦
               ¦к  файлу   ¦                  ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦(Зарезер-  ¦////////////////////////////////////////////
         5D    ¦вировано)  ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Блокировка/¦                       ¦
         5E    ¦/освобожде-¦                       ¦
               ¦ние доступа¦                       ¦
               ¦к  файлу   ¦                       ¦
               ГДДДДДДДДДДДґ                       ¦
               ¦00 Получе- ¦                       ¦
               ¦ние имени  ¦                       ¦
               ¦вычислитель¦                       ¦

                                      - 13-20 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       41    ¦Удалить    ¦        ¦
            Прерывание     ¦    Версия операционной системы MS-DOS                   ¦файл из    ¦        ¦
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД             ¦заданного  ¦        ¦
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦                  ¦каталога   ¦  Нет   ¦                   Да
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0              ¦(UNLINK)   ¦        ¦
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦            ДДДДДД†ДДДДДДДДДДДґ        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД       42    ¦Установить ¦        ¦
               ¦ной машины ¦                       ¦
               ГДДДДДДДДДДДґ           Нет         ¦           Да
               ¦02 Задание ¦                       ¦
               ¦установоч- ¦                       ¦
               ¦значений   ¦                       ¦
               ¦печатающего¦                       ¦
               ¦устройства ¦                       ¦
               ¦(принтера) ¦                       ¦
               ГДДДДДДДДДДДґ                       ¦
               ¦03 Получе- ¦                       ¦
               ¦ние устано-¦                       ¦
               ¦вочных     ¦                       ¦
               ¦значений   ¦                       ¦
               ¦печатающего¦                       ¦
               ¦устройства ¦                       ¦
               ¦(принтера) ¦                       ¦
         ДДДДДД†ДДДДДДДДДДДґ                       ¦
         5F    ¦02 Получе- ¦                       ¦
               ¦ние спискаД¦                       ¦
               ¦переадреса-¦                       ¦
               ¦ций        ¦                       ¦
               ГДДДДДДДДДДДґ                       ¦
               ¦03 Переад- ¦           Нет         ¦           Да
               ¦ресация    ¦                       ¦
               ¦устройства ¦                       ¦
               ГДДДДДДДДДДДґ                       ¦
               ¦04 Отменить¦                       ¦
               ¦переадреса-¦                       ¦
               ¦цию        ¦                       ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         60-   ¦(Зарезер-  ¦////////////////////////////////////////////
         -61   ¦вировано)  ¦////////////////////////////////////////////
               ¦           ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         62    ¦Получить   ¦                  ¦
               ¦адрес пре- ¦                  ¦
               ¦фикса теку-¦                  ¦
               ¦щего про-  ¦        Нет       ¦             Да
               ¦граммного  ¦                  ¦
               ¦сегмента   ¦                  ¦
               ¦(PSP)      ¦                  ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         63-   ¦(Зарезер-  ¦////////////////////////////////////////////
         -64   ¦ вировано) ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         65    ¦Получение  ¦                                 ¦
               ¦расширенных¦                                 ¦
               ¦технических¦                                 ¦
               ¦данных, от-¦                                 ¦
               ¦носящихся к¦                                 ¦
               ¦стране, где¦                                 ¦
               ¦установлено¦                                 ¦

                                      - 13-21 -
         ДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД       41    ¦Удалить    ¦        ¦
            Прерывание     ¦    Версия операционной системы MS-DOS                   ¦файл из    ¦        ¦
         ДДДДДДВДДДДДДДДДДД†ДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДД             ¦заданного  ¦        ¦
         Номер ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦                  ¦каталога   ¦  Нет   ¦                   Да
         преры-¦ Описание  ¦1.0¦ 1.1¦ 2.0¦ 2.1¦ 3.0¦ 3.1¦ 3.2¦ 3.3¦ 4.0              ¦(UNLINK)   ¦        ¦
         вания ¦           ¦   ¦    ¦    ¦    ¦    ¦    ¦    ¦    ¦            ДДДДДД†ДДДДДДДДДДДґ        ¦
         ДДДДДД†ДДДДДДДДДДД†ДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДД       42    ¦Установить ¦        ¦
               ¦оборудован.¦                                 ¦
         ДДДДДД†ДДДДДДДДДДДґ                                 ¦
         66    ¦Получение/ ¦                                 ¦
               ¦/установка ¦                                 ¦
               ¦страницы   ¦              Нет                ¦    Да
               ¦кодов      ¦                                 ¦
               ¦глобальной ¦                                 ¦
               ¦информации ¦                                 ¦
         ДДДДДД†ДДДДДДДДДДДґ                                 ¦
         67    ¦Установка  ¦                                 ¦
               ¦счетчика   ¦                                 ¦
               ¦описателя  ¦                                 ¦
         ДДДДДД†ДДДДДДДДДДДґ                                 ¦
         68    ¦Выполнить  ¦                                 ¦
               ¦файл       ¦                                 ¦
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
         69-   ¦(Зарезер-  ¦////////////////////////////////////////////
         -6B   ¦вировано)  ¦////////////////////////////////////////////
               ¦           ¦////////////////////////////////////////////
         ДДДДДД†ДДДДДДДДДДД†ДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДДДД
               ¦Расширенная¦                                      ¦
         6C    ¦функция    ¦                                      ¦
               ¦открытия/  ¦              Нет                     ¦  Да
               ¦/создания  ¦                                      ¦
         ДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДД
              По таблице  13-2 мы видим,  что функции операционной системы
         MS-DOS задаются в разных версиях в  разных  сочетаниях.  Диапазон
         всех  функций можно поделить на "функциональные" группы,  которые
         случайно,  но не всегда имеют тенденцию определять границы  между
         различными версиями операционной системы MS-DOS. Ниже описываются
         указанные "функциональные" группы.


                            Группа завершения программы

              Единственной функцией  в этой группе является функция 0. Эта
         функция почти идентична прерыванию "int 20h". Несмотря на то, что
         прерывание "int 20h" описывается  как "завершение программы" поч-
         ти во всех реализациях операционной  системы  MS-DOS,  вы  должны
         вместо  него использовать функцию 0 с целью избежания использова-
         ния команды INT.  Вам следует знать о том,  что во всех руководс-
         твах по операционной системе MS-DOS,  начиная с версии 2.0 и выше
         содержится рекомендация относительно того,  что функция 4Ch ("За-
         вершение  процесса",  что  известно также под названием "Выход" -
         EXIT) должна использоваться в качестве "предпочтительного" спосо-
         ба завершения выполнения программы.  Не следует однако, забывать,
         что функция 4Ch отсутствует в  версиях,  предшествовавших  версии
         2.00.
              Всегда следовать совету о завершении  программы,  данному  в
         руководстве  по  операционной  системе,  - положительное качество
         программиста.  Мы настойчиво рекомендуем вам всегда  пользоваться
         функцией 4Ch для завершения ваших программ в операционной системе

                                      - 13-22 -
         MS-DOS версий 2.00 и выше.  Если вы хотите,  чтобы ваши программы
         работали под управлением всех версий операционной системы MS-DOS,
         вам необходимо пользоваться функцией "получение версии DOS" (30h)
         для определения какой код нужно использовать для завершения прог-
         раммы:  для версий операционной системы MS-DOS 1.0 и 1.1  следует
         использовать функцию 0,  а для всех остальных версий операционной
         системы MS-DOS следует пользоваться функцией 4Ch.

              Группа стандартного ввода-вывода с символьных устройств
                                    (01h - 0Ch)

              В эту группу включаются функции с 01h по 0Ch.  Они использу-
         ются для ввода данных с клавиатуры и  для  вывода  информации  на
         дисплей-консоль,  на печатающее устройство (принтер), а также для
         ввода и вывода данных с и на дополнительные (логические) устройс-
         тва.  Указанные функции работают одинаково во всех версиях опера-
         ционной системы MS-DOS. Кроме того, по своей природе они сходны с
         аналогичным диапазоном функций операционной системы CP/M.

           Группа стандартного управления файлами (0Dh - 24h, 27h - 29h)

              В эту группу включаются функции с 0Dh по 24h и с 27h по 29h.
         Использование этих функций для работы с файлами обеспечивает сов-
         местимость работы во всех версиях  операционной  системы  MS-DOS.
         Некоторые из этих функций подобны функциям из аналогичного диапа-
         зона функций,  используемых в операционной системе CP/M. Несмотря
         на то,  что некоторые замысловатые функции для управления файлами
         были введены уже в версии 2.00 операционной системы  MS-DOS  (что
         описывается ниже),  применяя их, внимательно изучите их совмести-
         мость с другими версиями.  В разделе,  где описывается управление
         файлами  (этот  раздел  расположен  сразу после конца этой главы)
         также содержится важная информация относительно использования той
         или иной группы функций. Вам необходимо знать это.

                Стандартные  функции,  не связанные с устройствами
                               (25h,26h, 2Ah - 2Eh)

              В эту группу включаются функции 25h, 26h и функции с 2Ah по
         2h.  Заметим,  что  функции 2h является самой старшей из функций,
         поддерживаемых операционной системой MS-DOS версий,  предшествую-
         щих версии 2.00. Указанные функции выполняют множество разных за-
         дач,  не связанных с применяемыми устройствами: определение и ус-
         тановка  текущих  времени  и даты,  установка вектора прерывания,
         создание нового программного сегмента, установка или сброс стату-
         са  верификации.  Все эти функции являются принадлежностью только
         операционной системы MS-DOS. В операционной системе CP/M нет ана-
         логичных  функций.  Все указанные функции успешно выполняются при
         работе под управлением всех версий операционной  системы  MS-DOS,
         но хочется особое внимание обратить на работу функции 25h ("Уста-
         новка вектора прерывания"). Для выполнения этой функции требуется
         наличие двух условий: адрес стандартной программы управления пре-
         рываниями должен быть загружен в регистре DX и в  сегмент  данных
         (DS:DX),  а  номер  прерывания должен быть загружен в регистр Al.
         Поскольку данная функция имеет дело  с  прерываниями,  соблюдайте
         осторожность  при  ее использовании,  поскольку она может сделать
         Вашу программу несовместимой для выполнения в  других реализациях
         операционной системы MS-DOS и в других аппаратных средах.

                                      - 13-23 -


             Группа расширенных (общих) функций (2Fh - 38h, 4Ch - 4Fh,
                            54h - 57h, 59h - 5Fh, 62h)

              Эта группа функций охватывает функции, работающие в операци-
         онной системе MS-DOS версий с 2.00 по 3.10.  Функции с 59h по 5Сh
         и функция 62h имеются только в версиях 3.0 и  старше операционной
         системы MS-DOS,  а функции 5h и 5Fh имеются только в версиях 3.10
         и выше. Ни одна из указанных функций не доступна в версиях опера-
         ционной  системы  MS-DOS ниже 2.00.  Кроме этого,  в операционной
         системе  MS-DOS  версии  3.10 функции 32h, 34h, 37h с 50h по 53h,
         55h, 58h, 5Dh, 60h и 61h зарезервированы (не определены для испо-
         льзования). Функции, существующие во всех версиях,  работают пра-
         вильно и при переходе от одной версии к другой при следующих иск-
         лючениях:

             1. Функция 38h ("Информация,  относящаяся к стране, где уста-
                новлено оборудование. Под управлением операционной системы
                MS-DOS версий 3.00 и выше эта функция может использоваться
                для установки информации, относящейся к конкретной стране,
                а также  для ее нахождения.  Однако,  в версиях, начиная с
                версии  2.00 по 3.00 (но не включая версию 3.00) эта функ-
                ция может использоваться только для поиска  этой  информа-
                ции.

              2. Функция   44h   ("Управление   устройством   ввода-вывода
                 [IOCTL])
                Эта функция  имеет  два  новых  дополнительных параметра в
                операционной системе MS-DOS версии  3.00,  предназначенных
                для  поддержания драйверов устройств (регистр AL = 08h для
                проверки сменного носителя и регистра BL = 0Bh для измене-
                ния счетчика количества попыток исправления сбоя в блочном
                устройстве). В операционной системе MS-DOS версии 3.10 бы-
                ли  добавлены еще два параметра для проверки переадресации
                в сети (при значении регистра AL = 09h происходит проверка
                устройства, в то время, как при значении регистра AL = 0Ah
                происходит проверка управления файлом или устройством).

              3. Функции 5Eh  и 5Fh
                Эти функции  поддерживаются  только  в  версиях 3.1 и выше
                операционной системы MS-DOS. Они используются только в се-
                тевых средах. Каждая функция делится на несколько подфунк-
                ций.  Все они загружаются в регистр AХ в виде четырехзнач-
                ных  шестнадцатиричных  (16-битовых) номеров функций,  две
                последние цифры которых указывают конкретную  функцию (или
                подфункцию).  Функция  5E0h  используется для поиска имени
                вычислительной машины ,  подсоединенной к той же сети, что
                и вычислительная машина, производящая обращение к функции.
                Функция 5E0h используется  для  инициализации  печатающего
                устройства (принтера),  подсоединенного к сети и совместно
                используемого несколькими компьютерами. Функции  5F02h  по
                5F03h  используются  для  управления переадресации  данных
                в сети: функция 5F03h  меняет адрес  устройства,   функция
                5F02h ведет поиск информации по переадресации,  а  функция
                5F04h отменяет процедуру переадресации.


                                      - 13-24 -

                Группа функций работы с каталогом (39h - 3Bh, 47h)

              Эта группа  включает в себя функции,  начиная с 39h по 3Bh и
         функцию 47h. Эти функции существуют в версиях операционной систе-
         мы MS-DOS,  начиная с 2.00 и выше.  Эти функции выполняют команды
         работы с  каталогом:  функция  39h  создает  подкаталог  (команда
         MKDISK  или  команда  MD),  функция  3Al удаляет каталог (команда
         RMDIR или команд RD) и функция 3Bh меняет текущий каталог на дру-
         гой (команда CHDIR или команда CD).  Функция 47h используется для
         поиска информации в текущем каталоге (как если бы команда CD бы-
         ла введена без параметров).


                  Группа управления памятью/процессом (48h - 4Bh)

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

                                    Коды ошибок

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

           Коды критических и тяжелых ошибок, полученных при прерывании
                                     "Int 24h"

              В операционной  системе MS-DOS версии 1.0 процессом возврата
         кода ошибок управляет исключительно вектор прерывания  "int 24h".
         Все эти коды ошибок представляют собой ошибки, связанные со сбоя-
         ми аппаратных средств и считаются серьезными или  критическими по
         своей природе.  Эти же самые коды и механизм выдачи отчетов о них
         поддерживаются во всех более поздних версиях, несмотря на то, что
         некоторые  новые коды ошибок появлялись уже в версии 2.0 операци-
         онной системы MS-DOS.
              Для того, чтобы прикладная программа могла взаимодействовать
         с механизмом выдачи отчетов об ошибках,  исходный текст программы
         должен сохранить вектор прерывания "int 24h" и заменить его одним
         из векторов,  указывающим на  стандартную  программу  исправления
         ошибок.  Прежде,  чем произойдет завершение работы программы, ис-

                                      - 13-25 -
         ходный вектор прерывания "int 24h"  должен  быть  восстановлен  в
         свое  исходное  состояние.  Указанный механизм при его работе под
         управлением операционной системы MS-DOS версии 2.0 может  возвра-
         щать до семи кодов, а под управлением операционной системы MS-DOS
         версии 3.0 и выше.
              В таблице 13-3 перечисляются коды и указывается,  которые из
         них поддерживаются операционной системой MS-DOS только версий 2.0
         и выше.  Коды критических ошибок,  представленные в таблице 13-3,
         могут  также быть найдены при помощи другого механизма выдачи от-
         четов об ошибках,  представленного в версии 2.0 операционной сис-
         темы MS-DOS.  При работе под управлением этой версии операционной
         системы определенные обращения к функциям возвращают  коды ошибок
         при возникновении ошибочных ситуаций. Этот механизм описывается в
         разделе, следующем за таблицей 13-3.


             Коды возврата ошибок обращения к функциям (только версий
                      2.0 и выше операционной системы MS-DOS)

              Начиная с версии 2.0 операционной системы  MS-DOS  некоторые
         вызываемые  функции возвращают коды ошибок в определенных регист-
         рах,  если ошибка возникла в результате выполнения функции.  Если
         ошибка произошла, признак переполнения устанавливается в значение
         "1" и соответствующий регистр следует проверить (если   поддержи-
         вается этой функцией) на наличие в нем кода ошибки.  Если признак
         переполнения пуст, вы можете предполагать, что ошибок не возника-
         ло.  Критические или тяжелые ошибки, описанные выше (и определяе-
         мые при помощи механизма прерывания "int  24h"),  также  выдаются
         этим механизмом, несмотря на то, что при этом используются разные
         значения кодов.  При работе под управлением операционной  системы
         MS-DOS версий с 2.0 по 3.1 следующие функции возвращают коды оши-
         бок в регистре Ax, если признак переполнения устанавливается пос-
         ле их выполнения:  с 38h по 4Bh, 4h, 4Fh, 56h, 57h с 5Ah по 5Ch и
         с 5h по 5h.  Al - половина регистра Ax всегда должна  проверяться
         на наличие в ней кода ошибок, потому  что некоторые функции  воз-
         вращают в AH-половине другую информацию.  Для всех  этих  функций
         наличие нуля ("0") в регистре AL говорит об отсутствии ошибок.
              В таблице  13-4  перечислены все коды ошибок,  которые могут
         быть возвращены после обращения к функции. Версия (версии) опера-
         ционной системы MS-DOS, которая обеспечивает выдачу каждого кода,
         также указывается в таблице.  Отметим также, что коды ошибок с 19
         по  31 соответствуют кодам ошибок прерывания "int 24h" с 0 по Ch,
         а код ошибки 34 соответствует коду ошибки Fh прерыванию int 24h.


                                      - 13-26 -
                                                         Таблица 13-3
                 Критические коды ошибок (по прерыванию "Int 24h")
         ДДДДДДВДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
               ¦                     ¦Версия операционной системы MS-DOS
          Код  ¦     Описание        ГДДДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДД
         ошибки¦                     ¦  1.XX  ¦  2.XX  ¦  3.XX  ¦  4.XX
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДБДДДДДДДДБДДДДДДД
          0    ¦Попытка записи на    ¦        ¦
               ¦защищенный от записи ¦  Да    ¦
               ¦диск                 ¦        ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          1    ¦Неизвестное          ¦  Нет   ¦
               ¦устройство           ¦        ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          2    ¦Дисковод не готов    ¦  Да    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          3    ¦Неизвестная команда  ¦  Нет   ¦             Да
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          4    ¦Ошибка данных (CRC)  ¦  Да    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          5    ¦Неправильная длина   ¦        ¦
               ¦запроса              ¦  Нет   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          6    ¦Ошибка поиска        ¦  Да    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          7    ¦Неизвестный тип      ¦        ¦
               ¦носителя             ¦  Нет   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          8    ¦Сектор  не  найден   ¦  Да    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          9    ¦Конец бумаги в       ¦        ¦             Да
               ¦принтере             ¦  Нет   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          A    ¦Ошибка записи        ¦  Да    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          B    ¦Ошибка чтения        ¦  Нет   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДґ
          C    ¦Общий сбой           ¦  Да    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДДВДДДДДДДДВДДДДДД
          D    ¦Не определено        ¦ /////  ¦ /////  ¦ /////  ¦ /////
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДД
          E    ¦Не определено        ¦ /////  ¦ /////  ¦ /////  ¦ /////
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДБДДДДДДДД†ДДДДДДДДБДДДДДД
          F    ¦Неправильная смена   ¦       Нет       ¦       Да
               ¦диска                ¦                 ¦
         ДДДДДДБДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДД



                                      - 13-27 -
                                                         Таблица 13-4
                Коды ошибок обращения к функциям (только для версий
                     2.0 и старше операционной системы MS-DOS)
         ДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Код  ¦                           ¦    Версия операционной
         ошибки¦                           ¦       системы MS-DOS
         (шест-¦     Описание              ГДДДВДДДВДДДВДДДВДДДВДДДВДДД
         надц.)¦                           ¦2.0¦2.1¦3.0¦3.1¦3.2¦3.3¦4.0
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДБДДДБДДДБДДДБДДДБДДДБДДД
          1    ¦Неправильный  номер функции¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          2    ¦Файл не найден             ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          3    ¦Путь доступа не найден     ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          4    ¦Слишком много открытых     ¦
               ¦файлов                     ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          5    ¦Отказ  в  доступе          ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          6    ¦Неправильный  описатель    ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ                 Да
          7    ¦Разрушены управляющие      ¦
               ¦блоки памяти               ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          8    ¦Недостаточно памяти        ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          9    ¦Неправильный  адрес блока  ¦
               ¦памяти                     ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          A    ¦Неправильная среда         ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          B    ¦Неправильный формат        ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          C    ¦Неправильный код доступа   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          D    ¦Неправильный данные        ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          E    ¦(Зарезервировано)          ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
          F    ¦Задан неправильный дисковод¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         10    ¦Попытка удаления текущего  ¦
               ¦каталога                   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         11    ¦Не то же устройство        ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         12    ¦Больше  нет файлов         ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         13    ¦ Ошибка 0 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         14    ¦ Ошибка 1 прерывания       ¦                 Да
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         15    ¦ Ошибка 2 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         16    ¦ Ошибка 3 прерывания       ¦

                                      - 13-28 -
         ДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Код  ¦                           ¦    Версия операционной
         ошибки¦                           ¦       системы MS-DOS
         (шест-¦     Описание              ГДДДВДДДВДДДВДДДВДДДВДДДВДДД
         надц.)¦                           ¦2.0¦2.1¦3.0¦3.1¦3.2¦3.3¦4.0
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДБДДДБДДДБДДДБДДДБДДДБДДД
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         17    ¦ Ошибка 4 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         18    ¦ Ошибка 5 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         19    ¦ Ошибка 6 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         1A    ¦ Ошибка 7 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         1B    ¦ Ошибка 8 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         1C    ¦ Ошибка 9 прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         1D    ¦ Ошибка A прерывания       ¦                 Да
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         1E    ¦ Ошибка B прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
         1F    ¦ Ошибка C прерывания       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         20    ¦Нарушение совместного      ¦       ¦
               ¦использования (разделения) ¦       ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ       ¦
         21    ¦Нарушение блокировки       ¦       ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  Нет  ¦        Да
         22    ¦ Ошибка F прерывания       ¦       ¦
               ¦"Int 24h"  (таблица 13-3)  ¦       ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ       ¦
         23    ¦Недоступен блок FCB        ¦       ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         24    ¦Переполнение буфера сов-   ¦///¦///¦///¦///¦///¦
               ¦местного использования     ¦///¦///¦///¦///¦///¦   Да
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         25-   ¦(Зарезервировано)          ¦///¦///¦///¦///¦///¦///¦///
         41    ¦                           ¦///¦///¦///¦///¦///¦///¦///
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         42    ¦Сетевой запрос не          ¦           ¦
               ¦поддерживается             ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         43    ¦Удаленный компьютер не на- ¦           ¦
               ¦ходится в режиме прослуши- ¦           ¦
               ¦вания                      ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         44    ¦Повторение имени в сети    ¦           ¦

                                      - 13-29 -
         ДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Код  ¦                           ¦    Версия операционной
         ошибки¦                           ¦       системы MS-DOS
         (шест-¦     Описание              ГДДДВДДДВДДДВДДДВДДДВДДДВДДД
         надц.)¦                           ¦2.0¦2.1¦3.0¦3.1¦3.2¦3.3¦4.0
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДБДДДБДДДБДДДБДДДБДДДБДДД
         45    ¦Имя сети не найдено        ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         46    ¦Сеть занята                ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ   Нет     ¦        Да
         47    ¦Сетевое устройство         ¦           ¦
               ¦больше не существует       ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         48    ¦Превышено ограничение,нало-¦           ¦
               ¦женное на команду BIOS сети¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         49    ¦Ошибка адаптера аппаратных ¦           ¦
               ¦средств сети               ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         4A    ¦Неправильный отзыв от сети ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         4B    ¦Неожиданная ошибка сети    ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         4C    ¦Несовместимый удаленный    ¦           ¦
               ¦адаптер                    ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         4D    ¦Очередь печатающего        ¦           ¦
               ¦устройства (принтера) полна¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         4E    ¦Очередь не заполнена       ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         4F    ¦Для распечатки файла       ¦           ¦
               ¦недостаточно места         ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         50    ¦Имя сети было удалено      ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         51    ¦Отказ в доступе            ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         52    ¦Неправильный тип сетевого  ¦           ¦
               ¦устройства                 ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ   Нет     ¦        Да
         53    ¦Не найдено сетевое имя     ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         54    ¦Превышено ограничение      ¦           ¦
               ¦на сетевое имя             ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         55    ¦Превышено ограничение      ¦           ¦
               ¦сетевого сеанса BIOS       ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         56    ¦Временная остановка        ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         57    ¦Сетевой запрос не принят   ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ           ¦
         58    ¦Приостановка переадресации ¦           ¦
               ¦печати/диска               ¦           ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         59-   ¦(Зарезервировано)          ¦///¦///¦///¦///¦///¦///¦///
         5F    ¦                           ¦///¦///¦///¦///¦///¦///¦///
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД

                                      - 13-30 -
         ДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДД
          Код  ¦                           ¦    Версия операционной
         ошибки¦                           ¦       системы MS-DOS
         (шест-¦     Описание              ГДДДВДДДВДДДВДДДВДДДВДДДВДДД
         надц.)¦                           ¦2.0¦2.1¦3.0¦3.1¦3.2¦3.3¦4.0
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДБДДДБДДДБДДДБДДДБДДДБДДД
         60    ¦Файл уже существует        ¦   Нет ¦        Да
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         61    ¦(Зарезервировано)          ¦///¦///¦///¦///¦///¦///¦///
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         62    ¦Невозможно выполнить       ¦       ¦
               ¦<функция>                  ¦       ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ       ¦
         63    ¦Сбой по прерыванию         ¦   Нет ¦        Да
               ¦<Int 24h>                  ¦       ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД†ДДД
         64    ¦Неизвестная структура      ¦                   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ                   ¦
         65    ¦Уже назначено              ¦                   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ                   ¦
         66    ¦Неправильный пароль        ¦       Нет         ¦  Да
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ                   ¦
         67    ¦Неправильный параметр      ¦                   ¦
         ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДґ                   ¦
         68    ¦Сбой по записи в сети      ¦                   ¦
         ДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДБДДДДДДД

              Расширенная информация по ошибкам обращения к функциям
               (для версий 3.0 и выше операционной  системы MS-DOS)

              Поскольку мы заботимся о совместимости между  всеми версиями
         операционной системы MS-DOS, невозможно было включать управляющую
         информацию по кодам завершения во все новые  и  уже  существующие
         обращения к функциям в более поздних версиях.  Следовательно, для
         того,  чтобы расширить возможности операционной системы MS-DOS по
         управлению ошибками, в версии 3.0 операционной системы MS-DOS был
         введен новый механизм под названием "Расширенный код ошибок". При
         работе под управлением версии 3.0 и всех последующих версий, если
         при выполнении какой-либо функции устанавливается в единицу приз-
         нак переноса или регистр AL содержит значение FFh, дополнительная
         подробная информация об ошибках может быть найдена  путем  немед-
         ленной  загрузки 0 в регистр BХ и выдачей после этого обращения к
         функции 59h (Дать расширенную информацию об  ошибке).  Информация
         возвращается в виде, представленном ниже в таблице 13-5.

                                                         Таблица 13-5
                         Расширенная информация об ошибке
         ДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
              Регистр              ¦           Содержание
         ДДДДДДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
              AX                   ¦   Код ошибки (см. табл. 13-4)
              ВН                   ¦   Класс ошибки
              BL                   ¦   Предлагаемое действие
              CH                   ¦   Местоположение
         ДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                                    Код ошибки

              Код ошибки,  возвращаемый в регистр AX,  может быть любым из

                                      - 13-31 -
         представленных выше в таблице 13-4 в зависимости от версии опера-
         ционной системы MS-DOS.

                                   Класс ошибки

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

                                                         Таблица 13-6
                                   Классы ошибок
         ДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Значение     ¦                    Определение
         ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
              1          ¦    Истощение ресурса (нехватка памяти,
                         ¦    каналов и т.д.)
              2          ¦    Временно возникшая ситуация (проблема
                         ¦    может исчезнуть сама собой, например,
                         ¦    такая, как блокировка файла)
              3          ¦    Санкционированность доступа (отказ
                         ¦    в доступе)
              4          ¦    Внутренние сбои (операционная система
                         ¦    MS-DOS определила, что причиной ошибки
                         ¦    был внутренний дефект, а не действия
                         ¦    пользователя или системы)
              5          ¦    Сбой аппаратных средств (проблема выз-
                         ¦    вана не программой пользователя)
              6          ¦    Системный сбой (серьезный сбой в работе
                         ¦    программного обеспечения. Не обязате-
                         ¦    льно связано непосредственно со сбоем
                         ¦    в пользовательской программе - напри-
                         ¦    мер, зависит от пропущенных или отсут-
                         ¦    ствующих файлов конфигурации)
              7          ¦    Ошибка в прикладной программе (например,
                         ¦    противоречивые запросы)
              8          ¦    Не найден файл (или не найден какой-то
                         ¦    другой элемент)
              9          ¦    Неправильный формат (файл или какой-то
                         ¦    элемент заданы в неправильном формате)
              10         ¦    Заблокирован (файл или какой-то другой
                         ¦    элемент заблокирован внутренним образом)
              11         ¦    Носитель (сбой носителя, например, неис-
                         ¦    правность диска, ошибка CRC, не тот диск
                         ¦    установлен в дисковод или повреждена
                         ¦    поверхность носителя информации)
              12         ¦    Уже существует (трудности, связанные с
                         ¦    существующим элементом: с именем файла
                         ¦    или с именем вычислительной машины)
              13         ¦    Нераспознанный сбой (ошибка не принад-
                         ¦    лежит никакой категории или она непонят-
                         ¦    ная)
         ДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                               Предлагаемое действие

              В регистре  BL  возвращается  одно из значений,  указанных в

                                      - 13-32 -
         таблице 13-7. Здесь же предлагается действие по избавлению от ус-
         ловия ошибки.

                                                         Таблица 13-7
                    Предлагаемое действие по исправлению ошибки
         ДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Значение     ¦                    Определение
         ДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
              1          ¦   Повторить попытку  (повторить  попытку
                         ¦   несколько раз и если сбой останется, вы-
                         ¦   дать  подсказку пользователю с вопросом о
                         ¦   том, нужно ли продолжать работу программы
                         ¦   или ее следует аварийно завершить)
              2          ¦   Отложенная попытка повтора (то же самое,
                         ¦   что просто пункт 1 "Повторить попытку",
                         ¦   но сначала делается пауза с целью ожида-
                         ¦   ния: не исправит ли ошибка сама себя?
              3          ¦   Пользователь (подсказка пользователю сде-
                         ¦   лать повторный ввод - возможно в начале
                         ¦   был введен неправильный текст)
              4          ¦   Аварийное завершение (завершить программу
                         ¦   нормально после очистки)
              5          ¦   Немедленный выход (завершить программу
                         ¦   аварийно, не выполняя очистки)
              6          ¦   Игнорирование (ошибка может быть проигно-
                         ¦   рирована)
              7          ¦   Повторить попытку после вмешательства
                         ¦   (продолжить работу после такого вмеша-
                         ¦   тельства пользователя, как замена диска
         ДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                                  Местоположение

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

                                                         Таблица 13-8
                                Местоположение сбоя
         ДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Значение      ¦                   Определение
         ДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
              1           ¦  Неизвестная ошибка (неопределенная или
                          ¦  несоответствующая ситуации ошибка)
              2           ¦  Блочное устройство (ошибка относится к
                          ¦  носителю дисковой памяти)
              3           ¦  Сеть
              4           ¦  Последовательное устройство (ошибка от-
                          ¦  носится к последовательному соединению
                          ¦  или устройству)
              5           ¦  Память (ошибка относится к оперативной
                          ¦  памяти ОЗУ)
         ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

              Поскольку в  последних  версиях  операционной системы MS-DOS
         делались исправления в механизме управления ошибками, программис-
         ты встали перед  трудным  выбором.  Новое информационное средство

                                      - 13-33 -
         "Расширенный код ошибок" является,  очевидно, наиболее широко ис-
         пользуемым для разработки стандартных программ исправления ошибок
         внутри ваших программ.  Но стоимость этого механизма неприемлема.
         Если  вы должны включать этот механизм в вашу программу,  а также
         должны поддерживать  некоторые формы сквозной совместимости с бо-
         лее ранними версиями операционной системы MS-DOS,   полезной  для
         Вас может оказаться стандартная программа "Получить версию опера-
         ционной системы MS-DOS" (описана  ранее  в  этой главе). Для вер-
         сий  операционной системы MS-DOS ниже версии 2.0 Вы должны прове-
         рять  только  те  коды  ошибок,  которые  поддерживаются   данной
         версией.  Для версий 2.0 и 2.1 операционной системы MS-DOS Вы мо-
         жете расширить возможности по управлению  ошибками  и  обеспечить
         распознавание большего количества кодов ошибок. Для версий же 3.0
         и выше Вы можете даже больше расширить возможности  по управлению
         ошибками, используя вызов механизма "Расширенный код ошибок".

                                  Форматы дисков

              Как указывается в Главе 11 "Структура диска и восстановление
         файлов", некоторые форматы дисков поддерживаются разными версиями
         операционной системы MS-DOS.  В таблицах 13-9 и 13-10 собраны ха-
         рактеристики всех стандартных форматов 3,5-дюймовых,  5,25-дюймо-
         вых и 8-дюймовых гибких дисков,  поддерживаемых операционной сис-
         темой MS-DOS версий вплоть  до  4.0.  Более  подробно  информацию
         можно найти в Главе 11.
              Несмотря на то, что прочие форматы и типы дисков поддержива-
         ются в нескольких реализациях операционной системы MS-DOS, в таб-
         лице 13-9 представлены только те форматы гибких  дисков,  которые
         официально поддерживаются операционной системой MS-DOS. Аналогич-
         но,   не все характеристики жестких дисков описываются здесь, по-
         скольку многие их разновидности присущи только определенному виду
         реализации или системы.  Поддержка работы жестких дисков в  общем
         случае заключается в наличии системы ПЗУ BIOS.
              Допускается использование многих типов  и  размеров  жестких
         дисков, что зависит от версии и фирмы-изготовителя ПЗУ BIOS. Спе-
         циальные типы носителей информации такие,  как  "Bernoulli  Box",
         часто  требуют  использования специальных дисковых контроллеров и
         устанавливаемых дополнительно дисководов для того,  чтобы  справ-
         ляться с недостатком средств поддержки,  имеющимся  в большинстве
         реализаций системы ПЗУ BIOS.
              Операционная система  MS-DOS версий 2.0 по 3.30 поддерживает
         многие форматы жестких дисков с разделением  памяти,  достигающей
         максимального размера в 32 Мегабайта.
              Операционная система MS-DOS версий с 2.0 по 3.2 поддерживает
         только  один способ разделения памяти операционной системы DOS на
         жестком диске,  в то время как версия 3.3 поддерживает  несколько
         способов разделения памяти жесткого диска:  каждый с максимальным
         размером в 32 Мегабайта и каждому назначается имя дисковода. Опе-
         рационная система MS-DOS версии 4.0 (а  также операционная систе-
         ма "COMPAQ MS-DOS" версии 3.31)  поддерживает  разделение  памяти
         расширенного размера, которое может достигать 512 Мегабайт.
              Разделение памяти расширенного размера является дополнитель-
         ной возможностью в операционной системе MS-DOS версии 4.0:  боль-
         шой жесткий диск может, по-прежнему, форматироваться при несколь-
         ких способах разделения DOS   размером в 32 мегабайта или меньше.
         Отметим, что разделение в 32 байта или меньше используют 16-бито-
         вые номера секторов,  а разделения расширения регистра используют

                                      - 13-34 -
         32-битовые номера секторов. Это может вызвать проблемы, связанные
         с  несовместимостью  со многими прикладными программами,  которые
         обращаются к таблице размещения файлов (FAT) на диске  и  которые
         обращаются к секторам с 16-битовыми значениями.  Подробнее о фор-
         матах дисков можно узнать в главе 11.

                                                         Таблица 13-9
                Форматы гибких дисков операционной  системы MS-DOS
         ДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДД
                   ¦  Версия операционной системы MS-DOS   ¦    См.
          Характе- ГДДДДДВДДДДДВДДДДДВДДДДДВДДДДДВДДДДВДДДДґ Примеча-
          ристики  ¦ 1.0 ¦ 1.1 ¦ 2.0 ¦ 2.1 ¦ 3.0 ¦ 3.2¦ 3.3¦  ние 1
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДДВДДДДВДДД
         Размер    ¦5,25"¦5,25"¦5,25"¦5,25"¦5,25"¦3,5"¦3,5"¦ 8" ¦ 8" ¦ 8"
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Байт      ¦ FFE ¦ FFF ¦ FFC ¦ FFD ¦ FF9 ¦FF9 ¦FF0 ¦FFE ¦FFD ¦FFE
         формата   ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Стороны   ¦  1  ¦  2  ¦  1  ¦  2  ¦  2  ¦  2 ¦  2 ¦ 1  ¦ 2  ¦ 2
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Количество¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         треков на ¦ 40  ¦  40 ¦  40 ¦  40 ¦ 80  ¦ 80 ¦ 80 ¦77  ¦77  ¦77
         сторону   ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Количество¦  8  ¦  8  ¦  9  ¦  9  ¦  15 ¦  9 ¦ 18 ¦26  ¦26  ¦ 8
         секторов  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Количество¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         байтов в  ¦ 512 ¦ 512 ¦ 512 ¦ 512 ¦ 512 ¦512 ¦512 ¦128 ¦128 ¦1024
         секторе   ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Количество¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         секторов в¦  1  ¦  2  ¦  1  ¦  2  ¦  1  ¦  2 ¦  1 ¦ 4  ¦ 4  ¦ 1
         кластере  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Секторы   ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         начальной ¦  1  ¦  1  ¦  1  ¦  1  ¦  1  ¦  1 ¦  1 ¦ 1  ¦ 4  ¦ 1
         загрузки  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Секторы   ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         таблицы   ¦  1  ¦  1  ¦  2  ¦  2  ¦  7  ¦  3 ¦  9 ¦ 6  ¦ 6  ¦ 2
         FAT       ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Количество¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         таблиц FAT¦  2  ¦  2  ¦  2  ¦  2  ¦  2  ¦  2 ¦  2 ¦ 2  ¦ 2  ¦ 2
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Секторы   ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         корневого ¦  4  ¦  7  ¦  4  ¦  7  ¦ 14  ¦  7 ¦ 14 ¦ 17 ¦ 17 ¦ 6
         каталога  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Элементы  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         корневого ¦ 64  ¦ 112 ¦ 64  ¦ 112 ¦ 224 ¦ 112¦ 224¦ 68 ¦ 68 ¦192
         каталога  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Общее     ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         количество¦ 320 ¦ 640 ¦ 360 ¦ 720 ¦ 2400¦1440¦2880¦2002¦4004¦1232
         секторов  ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦

                                      - 13-35 -
         ДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДД
                   ¦  Версия операционной системы MS-DOS   ¦    См.
          Характе- ГДДДДДВДДДДДВДДДДДВДДДДДВДДДДДВДДДДВДДДДґ Примеча-
          ристики  ¦ 1.0 ¦ 1.1 ¦ 2.0 ¦ 2.1 ¦ 3.0 ¦ 3.2¦ 3.3¦  ние 1
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДДВДДДДВДДД
         Секторы   ¦ 313 ¦ 630 ¦ 351 ¦ 708 ¦ 2371¦1426¦2857¦1972¦3940¦1221
         данных    ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Общее     ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         количество¦ 313 ¦ 315 ¦ 351 ¦ 354 ¦2371 ¦713 ¦2857¦493 ¦985 ¦1221
         кластеров ¦     ¦     ¦     ¦     ¦     ¦    ¦    ¦    ¦    ¦
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Общая     ¦ 160 ¦ 320 ¦ 180 ¦ 360 ¦ 1,2 ¦720 ¦1.44¦501 ¦250,¦1,232
         емкость   ¦ Кб  ¦ Кб  ¦ Кб  ¦ Кб  ¦ Мб  ¦ Кб ¦ Мб ¦Кб  ¦25Кб¦ Мб
         ДДДДДДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДДД†ДДДД†ДДДД†ДДДД†ДДДД†ДДД
         Общая     ¦156,5¦ 315 ¦175,5¦ 354 ¦1,   ¦713 ¦1,  ¦246 ¦492,¦1,221
         емкость   ¦ Кб  ¦ Кб  ¦ Кб  ¦ Кб  ¦,1855¦ Кб ¦4285¦Кб  ¦5 Кб¦ Мб
         для данных¦     ¦     ¦     ¦     ¦ Мб  ¦    ¦ Мб ¦    ¦    ¦
         ДДДДДДДДДДБДДДДДБДДДДДБДДДДДБДДДДДБДДДДДБДДДДБДДДДБДДДДБДДДДБДДД


              Примечания:
              1.  значения  байта  описателя формата, используемые для
         идентификации формата 8-дюймового диска, являются такими  же,
         что используются для некоторых форматов 5,25-дюймовых дисков.
         Разные  значения  определяются  либо системой BIOS конкретной
         реализации  операционной  системы  MS-DOS,  либо  дисководом.
         Большая часть реализаций операционной системы MS-DOS, особен-
         но  те, в которых система BIOS расположена в ПЗУ, не содержат
         в системе BIOS необходимых стандартных программ для  8-дюймо-
         вых дисков. Таким образом, их поддержка осуществляется обычно
         при помощи специального драйвера дисковода. Поскольку первый
         8-дюймовый  формат  одинарной  плотности записи имеет то же
         значение байта описателя (FFF), что и последний формат (с удво-
         енной  плотностью  записи),  операционная  система  MS-DOS:
         по-разному  подходит к попыткам чтения диска: сначала система
         предполагает, что диск отформатирован с одинарной  плотностью
         записи. Если после прочтения первого сектора ошибок не возни-
         кает,  операционная  система  MS-DOS продолжает рассматривать
         диск, как диск с одинарной плотностью записи.
              Если после  прочтения  первого  сектора  диска  возникла
         ошибка,  операционная  система  MS-DOS предполагает, что диск
         отформатирован с удвоенной плотностью записи, и система снова
         повторяет попытку чтения первого сектора. Отметим также,  что
         некоторые системы поддерживают формат удвоенной плотности для
         односторонних, 8-дюймовых дисков, равную приблизительно поло-
         вине емкости двухсторонних дисков (610 Кбайт).

                                Управление файлами

              При работе  с  разными  версиями операционной системы MS-DOS
         следует обратить внимание на способ управления  файлами  в  Ваших
         программах.  Когда  операционная  система  MS-DOS только что была
         создана,  она обеспечивала  возможности  по  управлению  файлами,
         сходные  с  теми,  что  используются под управлением операционной
         системы CP/M (управляющая программа  для  микрокомпьютеров).  Это
         сходство сохранялось намеренно, поскольку оно предоставляло прог-
         раммисту сравнительно простой способ преобразования  как  8-бито-
         вых, так и 16-битовых программ из среды операционной системы СР/M

                                      - 13-36 -
         в среду операционной системы MS-DOS.  С целью поддержания совмес-
         тимости  все  версии операционной системы MS-DOS вплоть до версии
         3.1 имеют одинаковые  возможности по управлению файлами. В версии
         2.0 операционной системы MS-DOS   тем не менее, был внедрен новый
         способ,  который представляет собой главное отступление от метода
         управления  файлами,  использовавшегося  в  операционной  системе
         СР/M. Этот метод очень похож на метод управления файлами, исполь-
         зуемый  в операционной системе XENIX.  Несмотря на то,  что новый
         метод гораздо проще в использовании,  он, однако, не обеспечивает
         совместимости со старым способом, а, следовательно, требует повы-
         шенного внимания при работе с ним. Ниже описываются различия меж-
         ду двумя указанными методами.


                   Использование блоков управления файлами (FCB)

              Обращения к функциям с 0Fh по 29h, введенные в первой версии
         операционной системы MS-DOS,  используются вместе с блоком управ-
         ления файлом (FCB) для создания,  модификации и  удаления  файла.
         Блок FCB представляет собой сегмент программы, записанный в памя-
         ти, в котором определяются параметры файла, управляемого програм-
         мой. Операционная система MS-DOS и прикладная программа использу-
         ют параметры блока FCB для установления местоположения файла, его
         сегмента,  размера и других сведений об этом файле.  Однако,  по-
         скольку для действительного создания всего блока FCB не существу-
         ет обращений ни к каким функциям,  блок FCB должен уже быть зара-
         нее  определен  до  того,  как  в  программе  будут  использованы
         относящиеся к файлу обращения к функциям.  В любом случае, каждое
         из обращений к функциям, относящимся к файлу (с 0Fh по 29h), тре-
         бует того,  чтобы адрес памяти,  где расположен блок FCB,  был бы
         загружен в пару регистров DS:DX до начала выполнения функции. Это
         значит,  что прикладная программа должна сначала создать блок FCB
         и загрузить его в известный адрес либо в сегменте данных,  либо в
         области  данных  программного сегмента в памяти (в зависимости от
         того, что исходно задается программой).
              Когда операционная система MS-DOS загружает программу,  сис-
         тема создает и форматирует два блока FCB в  сегменте программного
         префикса  (PSP)  программы.  Адрес этих блоков FCB в префиксе PSP
         так же, как средства обращения к префиксу PSP, описываются в гла-
         ве 3.  Поля имен файлов заполняются из информации, вводимой в ко-
         мандной строке при вводе программы (как,  например,  в таком слу-
         чае: "A>MUNG infile outfile"). Если, однако, характеристика файла
         содержит имя пути доступа, действительным в блоке FCB будет толь-
         ко номер дисковода.  Кроме этого, в блоке FCB не будет появляться
         никаких директив по переадресации. И, наконец, отметим, что, если
         программа  открывает первый блок FCB в префиксе PSP,  второй блок
         FCB перезаписывается.
              В таблице  13-10 представлена структура блока FCB и там ука-
         зываются размеры и смещение адреса в памяти для каждого параметра
         в пределах блока FCB.  Заметим,  что не всеми параметрами в блоке
         FCB можно управлять из прикладной программы.  Некоторые модифици-
         руются только самой операционной системой MS-DOS,  а другие могут
         модифицироваться и программой и операционной системой  MS-DOS.  В
         любом  случае  при создании FCB следует предусматривать место для
         всех параметров.
              В таблице 13-10 поля с отрицательными смещениями используют-
         ся при работе под управлением версий 2.0  и  старше  операционной

                                      - 13-37 -
         системы MS-DOS, для превращения блока FCB в расширенный блок FCB,
         который позволяет вам использовать параметр атрибута файла в сме-
         щении "-1". Значение 0FFh должно располагаться в смещении - 7 для
         того,  чтобы блок FCB был бы обозначен, как расширенный блок FCB.

                                                         Таблица 13-10
                   Формат блока FCB операционной системы MS-DOS
         ДДДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДД
          Байт      ¦ Размер  ¦    Описание         ¦   Модифицируется
          смещения  ¦         ¦                     ¦
         ДДДДДДДДДДД†ДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДД
          - 7       ¦   1     ¦ Шестнадцатиричное   ¦   Программой
                    ¦         ¦ значение 0FF        ¦
          - 6       ¦   6     ¦ Зарезервировано     ¦   Программой
                    ¦         ¦ (должен быть нуль)  ¦
          - 1       ¦   1     ¦ Атрибут файла       ¦   Программой  и
                    ¦         ¦                     ¦   операционной
                    ¦         ¦                     ¦   системой MS-DOS
            0       ¦   1     ¦ Номер дисковода (от ¦   Программой
                    ¦         ¦ 0 до 16)            ¦   операционной
                    ¦         ¦                     ¦   системой MS-DOS
            1       ¦   8     ¦ Имя файла или       ¦   Программой
                    ¦         ¦ устройства          ¦
            9       ¦   3     ¦ Расширение файла    ¦   Программой
                    ¦         ¦ или его тип         ¦
            12      ¦   2     ¦ Текущий блок        ¦   Программой
            14      ¦   2     ¦ Размер записи в     ¦
                    ¦         ¦ байтах              ¦
            16      ¦   4     ¦ Размер файла в      ¦   Операционной
                    ¦         ¦ байтах              ¦   системой MS-DOS
            20      ¦   2     ¦ Дата                ¦   Операционной
                    ¦         ¦                     ¦   системой MS-DOS
            22      ¦   10    ¦ Зарезервировано     ¦   Операционной
                    ¦         ¦                     ¦   системой MS-DOS
            32      ¦   1     ¦ Текущая запись      ¦   Программой
                    ¦         ¦                     ¦   операционной
                    ¦         ¦                     ¦   системой MS-DOS
            33      ¦   4     ¦ Номер произвольной  ¦   Программой
                    ¦         ¦ записи              ¦   операционной
                    ¦         ¦                     ¦   системой MS-DOS
         ДДДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДД
              Значения смещения и размера записи задаются в десятичном
         виде.

                   Описатели файлов операционной системы MS-DOS

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


                                      - 13-38 -
                          drive:\path\filename.extension
                            1      2     3          4
         1 - дисковод; 2 - путь доступа; 3 - имя файла; 4 - расширение

              При выполнении  обращения к функции 3Ch (создать файл) или к
         функции 3Dh (открыть файл) операционная  система  MS-DOS  создает
         описатель файла, основываясь на информации, содержащейся в строке
         ASCIIZ.
              Обращение к функциям с 3Ch по 57h -  все являются функциями,
         связанными с файлами и использующими описатели файлов.  В эту  же
         группу включены три новые функции (с 5Аh по 5Ch),  введенные вер-
         сией 3.0 операционной системы MS-DOS.
              Поскольку операционная  система  MS-DOS  создает и управляет
         описателями файла,  прикладной программе больше не нужно отслежи-
         вать  местоположение информации о файле в памяти простого обраще-
         ния к строке ASCIIZ,достаточно для оповещения операционной систе-
         мы  MS-DOS  о  том,  что программа делает,  согласно используемой
         функции.  Это встроенное средство имеет также другое достоинство:
         одновременно может существовать несколько описателей файлов,  по-
         тому что операционная система MS-DOS всегда следит за их  положе-
         нием в памяти.
              Единственный недостаток использования описателей заключается
         в  том,  что  они  не поддерживаются версиями до 2.0 операционной
         системы MS-DOS.  Поэтому,  если программа должна быть совместимой
         со  всеми версиями операционной системы MS-DOS,  следует избегать
         использования описателей файлов. Отметим, однако, что при появле-
         нии описателей файлов (так  же, как и при появлении многих других
         особенностей) версии операционной системы MS-DOS с 2.0 по 3.1 до-
         казали, что они являются переходными ступенями между старыми опе-
         рационными системами (такими,  как операционная система  CP/M)  и
         наиболее совершенной из операционных систем - системой XENIX.
              Почти все новые обращения к функциям   связаны   с  файлами,
         операционные  системы  MS-DOS непосредственно совместимы с анало-
         гичными функциями операционной системы XENIX,  как это происходит
         с другими характеристиками системы,  такими, как имена путей дос-
         тупа,  древовидные структуры каталогов и переадресация. Таким об-
         разом, следует учитывать совместимость снизу вверх, особенно тог-
         да,  когда Вы осознаете,  что текущие версии операционной системы
         XENIX не поддерживают старый метод управления файлами с использо-
         ванием блоков FCB.

                Операционная система MS-DOS (персональный компьютер
                фирмы "ИБМ" (IBM PC) и персональный компьютер фирмы
                             "ИБМ" серии 2 (IBM PS/2)

              Персональный компьютер фирмы "ИБМ" (IBM PC)  безусловно  был
         самым  распространенным из всех,  когда-либо установленных компь-
         ютеров,  работавших под управлением операционной системы  MS-DOS.
         Действительно,  широкая  популярность операционной системы MS-DOS
         была ею завоевана, благодаря беспрецедентному успеху серии персо-
         нальных  компьютеров  и  совместимых с ними вычислительных машин.
         Как же операционная система MS-DOS,  используемая в  персональном
         компьютере IBM PC,  соотносится с реализациями, описанными в этой
         главе и в этой книге? При чтении руководства по операционной сис-
         теме MS-DOS для персональных компьютеров IBM PC и IBM PS/2, в ко-
         торых операционная система MS-DOS кратко называется "операционная
         система  DOS" или "операционная система PC-DOS"  и руководства по

                                      - 13-39 -
         операционной системе MS-DOS, выпущенного фирмой "Майкрософт", об-
         ратите  внимание  на  сходства  и на значительные различия в них.
         Сходства вы найдете в тех частях операционной системы MS-DOS, ко-
         торые  являются  стандартными  или "родовыми" для всех реализаций
         операционной системы MS-DOS.  Различия представляют собой те осо-
         бенности MS-DOS,  которые часто являются уникальными для конкрет-
         ных реализаций. В задачу настоящей книги входит описание стандарт-
         ного   процесса  программирования  в  среде  операционной  системы
         MS-DOS.  Акцент делается на тех моментах программирования, которые
         применимы ко всем реализациям операционной системы MS-DOS. Однако,
         поскольку в персональном компьютере IBM PC наиболее часто устанав-
         ливается  операционная  система  MS-DOS,  мы  должны прояснить все
         сходства и различия. Наличие такой информации поможет Вам при соз-
         дании Ваших программ обеспечивать их полную совместимость.

                                     Сходства

              Представленные ниже кодовые характеристики операционной сис-
         темы MS-DOS являются одинаковыми для всех реализаций этой  опера-
         ционной системы для любой версии.

              - Программа DOS (дисковая операционная система).  Эта  прог-
                рамма  по существу представляет собой операционную систему
                MS-DOS и размещается в скрытом файле  на  диске  начальной
                загрузки. В персональном компьютере IBM PC этот файл назы-
                вается IBMDOS.СОМ.  Несмотря на то, что он может быть наз-
                ван  и как-то иначе в других вычислительных машинах,  этот
                файл всегда одинаков для данной версии операционной систе-
                мы и состоит из перечисленных ниже частей:
                1. Исполнительного органа операционной системы
                2. Обращений к  функциям
                3. Органа управления памятью (не структуры памяти) раз-
                   мером до 640 Кбайт
                4. Интерфейса системы BIOS (не самой системы BIOS)

              - Интерфейсная программа системы BIOS. Интерфейсная програм-
                ма системы BIOS (базовая система ввода-вывода) выступает в
                роли интерфейса или транслятора между операционной  систе-
                мой MS-DOS и системой BIOS.  В персональном компьютере IBM
                PC этот интерфейс размещается на диске  начальной загрузки
                в  скрытом  файле с именем IBMBIO.СОМ.  Входная часть этой
                программы одинакова для всех версий  операционной  системы
                MS-DOS, а выходная часто зависит от типа конкретной вычис-
                лительной машины (это может  быть  персональный  компьютер
                IBM PC,  IBM PCjr, портативный IBM PC-Portable, IBM PC-XT,
                IBM PC-AT или IBM PS/2.  Операционная система DOS для сов-
                местимых  с  IBM PC персональных компьютеров имеет похожий
                файл,  но он называется как-то иначе. В некоторых реализа-
                циях операционной системы MS-DOS (в таких, как MS-PRO и PC
                -PRO для компьютеров CompuPro (Viasyn) этот файл замещает-
                ся самой системой BIOS.

                - Интерпретатор  команд (COMMAND.COM).  Этот нескрытый файл
                  присутствует на всех дисках начальной загрузки. Обычно он
                  бывает одинаковым для  всех  реализаций,  но иногда можно
                  встретить и различия в них. Этот файл обеспечивает интер-

                                     - 13-40 -
                фейс  между  операционной  системой MS-DOS и пользователем,
                выводя на экран дисплея подсказки. Он содержит такие встро-
                енные команды и функции, как DIR (вывести оглавление), COPY
                (скопировать),  RENAME  (переименовать),  ERASE(стереть)  и
                функцию переадресования.

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

                                     Различия

              Перечисленные ниже части операционной системы MS-DOS зависят
         от конкретной реализации системы:

              - Система BIOS.  В серии персональных компьютеров IBM PC,  а
                также  почти  во  всех совместимых с IBM-PC вычислительных
                машинах базовая система ввода-вывода  BIOS  расположена  в
                ПЗУ. Система BIOS содержит стандартные программы, выполня-
                ющие роль расширений операционной системы MS-DOS  в  части
                управления аппаратными средствами. Поскольку использование
                тех или других аппаратных средств всегда  основывается  на
                собственных  разработках производителя персональной техни-
                ки,  конструкция системы BIOS должна также быть всякий раз
                отдельной,  кроме тех случаев, когда она покупается у дру-
                гого производителя. Представленные ниже общие части систе-
                мы  BIOS  часто зависят от конкретной персональной машины:
                1. Механизмы управления аппаратными и программными
                   прерываниями.
                2. Стандартные программы для контроллеров диска и  драйве-
                   ров диска.
                3. Стандартные программы для консоли,  принтера  и  портов
                   связи.
                4. Прочие произвольные функции типа графических контролле-
                   ров и игровых адаптеров.

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

              - Дисководы. Цель: управление некоторыми уникальными особен-
                ностями системных аппаратных  средств.  Многие  системы  в
                настоящее время включают в себя дисководы   как часть опе-
                рационной системы MS-DOS. В серии персональных компьютеров
                IBM-PC ANSI.SYS привносит расширенные функции в мониторную

                                      - 13-41 -
                систему.  Аналогичный файл присутствует в некоторых других
                совместимых с IBM PC персональных компьютерах, но он редко
                встречается в вычислительных машинах, не совместимых с се-
                рией IBM PC.
              - Внешние команды. Специальные нестандартные внешние команды
                часто включаются в реализации операционной системы MS-DOS.

              В общем  самым  важным  различием в реализациях операционной
         системы MS-DOS является сама базовая система  ввода-вывода  BIOS,
         поскольку эта система содержит стандартные программы, требующиеся
         для уникальных аппаратных средств (таких,  как контроллеры диска,
         мониторы или терминалы и клавиатуры) вычислительной машины. Таким
         образом,  во время разработки программ следует внимательно  отно-
         ситься к требуемому уровню программной совместимости. Если Вы хо-
         тите, чтобы Ваша программа подходила для всех реализаций операци-
         онной  системы  MS-DOS,  никогда не пользуйтесь прямым доступом к
         системе BIOS и никогда не  пользуйтесь  такими  специальными  для
         каждой  системы  функциями  как  прерывания.  Если Вам необходимо
         пользоваться специальными для каждой системы  функциями,  но, по-
         прежнему, требуется полная совместимость, этими функциями следует
         управлять либо через драйверы устройств (дисководы), либо, если в
         наличии  имеется программа установки,  которая может осуществлять
         модификации для  конкретной  вычислительной  машины,  через  саму
         программу.
              Даже в пределах одной серии персональных компьютеров  IBM PC
         возникают проблемы совместимости.  Например, возможности программ
         системы BIOS,  расположенных в ПЗУ,  отличаются для  персональных
         компьютеров IBM PC,  IBM PC-XT и IBM PC-AT.  Несмотря на то,  что
         функции системы BIOS в персональном компьютере IBM PC  также  су-
         ществуют  и  в персональном компьютере IBM PC-XT,  этот последний
         компьютер предоставляет и дополнительные  функции.  Между  персо-
         нальными  компьютерами  IBM  PC-XT  и IBM АT существуют сравнимые
         различия. Если Вы неуверены или не точно знаете эти различия, об-
         ращайтесь  к  Справочным техническим руководствам фирмы "ИБМ" (по
         аппаратным средствам) по каждой конкретной машине. Полный листинг
         состава системы BIOS имеется в каждом руководстве.

                  Совместимость с другими операционными системами

              Как отмечено выше в этой главе,  операционная система MS-DOS
         тем или иным образом похожа на другие операционные системы.  Пер-
         вая версия операционной системы MS-DOS с обеих точек зрения  -  с
         точки зрения программиста и пользователя,  похожа на операционную
         систему CP/M.  Несмотря на то,  что многие свойства  операционной
         системы MS-DOS не существуют в операционной системе CP/M,  основ-
         ная структура и использование команд (например,  подсказки "DOS>"
         и командного файла с расширением ".СОМ"), по существу, идентичны.
         Операционная система MS-DOS версии 2.00,  однако, ввела несколько
         возможностей и функций,  взятых из гораздо более передовой опера-
         ционной системы  под  названием  XENIX,  также  созданной  фирмой
         "Майкрософт". (Операционная система XENIX представляет собой раз-
         новидность широко распространенного мини-компьютера   и  основной
         структуры  операционной системы под названием UNIX).  Такие функ-
         ции,  как переадресация файлов и устройств,  каналы,  дисководы и
         описатели файлов представляют собой производные подобных функций,
         имеющихся в операционной системы XENIX. В некоторых новых версиях
         операционной  системы  MS-DOS  некоторые более новые операционные

                                      - 13-42 -
         системы обеспечивают совместимость с МS-DOS.  Вероятно,  наиболее
         известными примерами будут персональные компьютеры "Concurrent PC
         DOS" и "Concurrent DOS286" фирмы "Дайджетал Ресерч, Инк." (исход-
         ного  разработчика  операционной  системы CP/M).  Ниже в разделах
         предлагается обзор сходств и различий операционной системы MS-DOS
         и совместимых или псевдосовместимых с ними операционных систем.


                           Операционная система CP/M-80

              После изучения архитектуры и возможностей операционной  сис-
         темы MS-DOS вы узнаете, что разработчики этой операционной систе-
         мы черпали свои идеи из сведений об  операционной  системе  СР/М,
         предназначенной для вычислительных машин,  созданных на базе мик-
         ропроцессоров 8080,  8085 и Z80.  До введения персональных компь-
         ютеров IBM PC с операционной системой MS-DOS, операционная систе-
         ма  CP/M  считалась   стандартной   операционной   системой   для
         микрокомпьютеров. Операционная система CP/M и поныне остается са-
         мой популярной операционной системой для  8-битовых (8-разрядных)
         вычислительных машин.  Когда производители компьютеров начали вы-
         нашивать планы создания  16-битовых  (16-разрядных)  компьютеров,
         используя  появившийся  тогда  микропроцессор 8086 фирмы "Интел",
         многим из них прошлось подождать, поскольку в то время 16-битовая
         версия  операционной системы CP/M (называемая теперь операционная
         система CP/M-86) была еще не готова.  Фирма под названием  "Сиэтл
         Компьютер  Продактс" ("Seattle Computer Products") оказалась впе-
         реди этого движения и разработала свою  собственную  операционную
         систему,  которую она назвала QDOS ("Быстрая и грязная операцион-
         ная система") и которая после нескольких доработок была позже пе-
         реименована в операционную систему 86-DOS.
              Архитектура операционной системы 86-DOS была очень похожа на
         архитектуру операционной системы CP/M,  но фирма "Сиэтл Компьютер
         Продактс" усовершенствовала многие ее функции и добавила несколь-
         ко  новых.  После  этого операционная система 86-DOS была продана
         фирме "Майкрософт", где была переименована в "операционную систе-
         му MS-DOS".  Эта первая версия операционной системы MS-DOS (кото-
         рая по существу явилась неизменной операционной системой 86-DOS )
         была принята для использования фирмой "ИБМ" в своих новых выпуска-
         емых персональных компьютерах - IBM PC.  После этого фирма "Майк-
         рософт" несколько расширила операционную систему MS-DOS,  что от-
         разилось в появлении версии  2.00.  Операционная  система  MS-DOS
         версии 2.0 сохранила большую часть функций первой версии.  Следо-
         вательно,  было сохранено сходство с операционной системой  CP/M,
         что  явилось большим удобством для программистов,  потому что при
         этом большинство программ для  операционной  системы  CP/M  могли
         быть легко преобразованы в программы для операционной системы MS-
         DOS. С точки зрения программиста важными для него являются следу-

                                     - 13-43 -
         ющие сходства операционных систем:

              - Обращения к функциям. Большая часть  обращений к  функциям
                -------------------- в первой версии  операционной системы
                MS-DOS, особенно те, которые относятся к функциям работы с
                файлами, очень похожа на обращения к функциям, обеспечива-
                емые  версиями 2.2 и 3.0 операционной системы СР/ М.  Нес-
                мотря на то, что использование регистров значительно отли-
                чается для 8-битовых вычислительных машин серии 8080/Z80 и
                семейства вычислительных машин,  основанных на  16-битовых
                микропроцессорах  8086,  способ,  которым происходит в них
                установка функций и выдача информации, весьма сходен. Даже
                некоторые  номера обращений к функциям одинаковы.  Функции
                операционной системы MS-DOS,   которые фактически являются
                идентичными  таким  же функциям операционной системы CP/M,
                включают в себя номера функций от 0 до 24  в  шестнадцати-
                ричном виде. Эти функции и выполняемые ими операции сохра-
                нены и в более поздних версиях операционной системы MS-DOS
                вплоть до версии 3.1.

              - Блоки FCB. Единственный способ, которым первая версия опе-
                --------- рационной системы MS-DOS могла создавать, откры-
                вать, изменять или удалять файл, заключалась в использова-
                нии блока управления файлами (FCB).  Формат блока FCB  при
                работе  под управлением операционной системы MS-DOS и спо-
                соб, которым он устанавливается, почти идентичен использо-
                ванию блока FCB под управлением операционной системы СР/M.
                Поскольку управление файлами является решающим  моментом в
                большинстве операционных систем, построенных на операцион-
                ной системе DOS, сходства в использовании блока FCB в опе-
                рационных  системах CP/M и MS-DOS бесценны для программис-
                тов. Несмотря на то, что новый механизм управления файлами
                был впервые представлен в операционной системе MS-DOS вер-
                сии 2.00,  все версии,  вплоть до версии 3.1, по-прежнему,
                сохраняют  (в  целях  обеспечения  совместимости) "старый"
                способ работы с блоками FCB.

              - Команды.   Использование   встроенных   команд  и  внешних
                --------  команд для работы с  программами  очень похоже в
                обеих операционных  системах.  Операционная  система  CP/M
                держит свои встроенные команды в так называемом процессоре
                консольных команд (ССР),  который является частью операци-
                онной системы при загрузке в память.  Операционная система
                MS-DOS управляет встроенными командами очень похожими  об-
                разом за исключением того,  что ее командный процессор су-
                ществует в дисковом файле,  называемом COMMAND.COM. Опера-
                ционная  система  MS-DOS также работает в 8-битовом режиме
                совместимости для внешних команд и таким образом управляет
                файлами  и  расширением ".СОМ" способом,  почти идентичным
                способу,  которым ими управляет операционная система СР/M.
                Под управлением операционной системы MS-DOS файлы с расши-
                рением .СОМ используют только 64-Кбайтный сегмент памяти,
                тем самым эмулируя использование памяти систем, основанных
                на микропроцессорах типа 8080 или Z80. Формат команд .EXE
                для работы их под управлением операционной системы MS-DOS,
                однако,применяется только в вычислительных машинах, исполь-
                зующих  микропроцессоры  серии 8086 и,  следовательно,  не
                совместимы с операционной системой CP/M.


                                      - 13-44 -

                Операционные системы СР/M-86 и "Concurrent CP/M-86"

             Операционная система СР/M-86 является 16-битовым счетным ме-
         ханизмом исходной операционной системы CP/M  для микрокомпьютеров
         семейства 8086.  Многие из этих особенностей, сохранившиеся еще с
         8-битовой версии операционной системы CP/M, похожи на особенности
         операционной системы MS-DOS.  Например, методы использования бло-
         ков FCB и обращений к функциям, связанным с файлами (включая опи-
         сатели  файлов)  в операционной системе СР/M-86,  очень похожи на
         методы, используемые в операционной системе MS-DOS.
              Вскоре после представления операционной системы СР/M-86, бы-
         ла введена новая версия под названием "Concurrent CP/M-86", кото-
         рая  привнесла  в  операционную систему CP/M такие режимы работы,
         как мультизадачный и оконный. Специальные версии обеих операцион-
         ных систем были созданы для персональных компьютеров IBM PC,  ко-
         торый пользовался этими новыми возможностями специальным образом.
         Большая часть функции операционной системы CP/M-86 была сохранена
         в операционной системе "Concurrent CP/M-86", но многие из них бы-
         ли  доработаны  и  усложнены вследствие появления мультизадачного
         режима работы в более новых операционных системах.

                     Операционные системы "Concurrent PC-DOS"
                              и "Concurrent DOS-286"

              С появлением операционной системы  MS-DOS,  как  фактической
         стандартной операционной системы для семейства 16-битовых  микро-
         процессоров 8086 (особенно в персональных компьютерах  IBM  PC  и
         совместимых с ними вычислительных машинах),  создатели операцион-
         ных систем CP/M поняли,  что им придется разработать некую  форму
         совместимости  с  операционной системой MS-DOS,  потому что боль-
         шинство пользователей работают на MS-DOS - ориентированных опера-
         ционных системах. Фирма "Дайджетал Ресерч, Инк." выпустила расши-
         ренную  версию  операционной  системы  "Concurrent  CP/M-86"  под
         названием  "Concurrent  PC-DOS",  которая  в совсем исходном виде
         обеспечивала совместимость с операционной системой  MS-DOS версии
         1.0.  Версия 3 операционной системы "Concurrent PC-DOS" представ-
         ляет собой еще более расширенный вариант,  который обеспечен сов-
         местимостью  уже с операционной системой MS-DOS версии 2.00.  Эта
         операционная система может параллельно использоваться  для работы
         как программ операционной системы CP/M-86,  так и программ опера-
         ционной системы MS-DOS, и может принимать все обращения к функци-
         ям,  поддерживаемым  эквивалентными версиями операционной системы
         MS-DOS.
              Другая разновидность    операционной   системы   "Concurrent
         PC-DOS" под названием "Concurrent DOS286" предназначена  для  вы-
         числительных машин,  использующих микропроцессор 80286 фирмы "Ин-
         тел".  Эта операционная система разработана для  использования  с
         микропроцессором  80286  в "виртуальном" (также называется "защи-
         щенном") режиме и обеспечивает диапазон адресации памяти в 16 Ме-
         габайт.  Эта операционная система также может быть запущена в ре-
         жиме "реального времени" (режим совместимости 8086) параллельно с
         виртуальным режимом так, что под ее управлением могут параллельно
         работать программы, написанные для операционной системы MS-DOS  и
         программы, написанные для операционной системы СР/M. Операционная
         система "Concurrent DOS286" обеспечивает  те  же  возможности  по
         совместимости  операционной  системы  MS-DOS,  что и операционная
         система "Concurrent PC-DOS". Занимаясь совместимостью этой опера-

                                      - 13-45 -
         ционной системы,  следует соблюдать осторожность, потому что пра-
         вильная ее работа в большой степени зависит от  версии  микропро-
         цессора  80286,  используемого  в  системе  (более  ранние версии
         микропроцессоров имеют трудности с переключением и подсоединением
         виртуального и реального режимов работы).

                         Операционные системы XENIX и UNIX

              Как замечено выше, более поздние версии операционной системы
         MS-DOS (начиная с версии 2.0) содержат  некоторые характеристики,
         имеющиеся в операционной системе XENIX - другой операционной сис-
         теме фирмы "Майкрософт".  Большинство характеристик,  имеющихся в
         операционной системе MS-DOS версии 2.0 (такие, как дисководы, пе-
         реадресация, каналы и описатели фалов) являются свойствами, осно-
         ванными на таких же свойствах операционной системы XEENIX,  кото-
         рые,  в свою очередь основываются на характеристиках операционной
         системы UNIX фирмы "AT&T".  Таким образом, несмотря на то, что Вы
         должны уделять внимание проблемам совместимости сверху вниз (опе-
         рационные системы MS-DOS и CP/M),  совместимость снизу вверх тоже
         должна приниматься во  внимание  и  рассматриваться,  потому  что
         XENIX  -  подобные  характеристики  операционной  системы  MS-DOS
         представляют собой указание на то,  что следует сохранять в даль-
         нейших разработках новых версий операционной системы MS-DOS.

                             Операционная система OS/2

              Программы операционной  системы  MS-DOS  никаким  образом не
         совместимы с работой защищенного режима  80286/80386 операционной
         системы OS/2. Операционная система OS/2, однако, имеет, так назы-
         ваемый "блок совместимости",  в котором может быть запущено боль-
         шинство  программ  операционной  системы MS-DOS в неизменном виде
         под  эмулятором  MS-DOS.  Блок совместимости операционной системы
         OS/2  работает  в реальном режиме микропроцессоров семейства 8086
         (1 Мегабайт адресуемой памяти,  из которой 640 Кбайт используются
         для операционной системы MS-DOS) и обеспечивает эмуляцию операци-
         онной системы MS-DOS, предоставляющую совместимость с версией 3.3
         операционной системы MS-DOS).  Однако,  из-за того, что блок сов-
         местимости в действительности не обеспечивает работу операционной
         системы MS-DOS,  а только эмулирует ее, совместимость этих опера-
         ционных систем не стопроцентная.  Например, программы, не исполь-
         зующие временные прерывания,  скорее всего будут работать в блоке
         совместимости.  Более того, некоторые из не входящих в документа-
         цию  обращений  к функциям по прерыванию "int 21h" могут не рабо-
         тать так,  как они работали под управлением реальной операционной
         системы MS-DOS могут отличаться от адресов в блоке совместимости.
         В общем случае,  так называемые "хорошо работающие программы" бу-
         дут работать в блоке совместимости без необходимости их модифика-
         ций.
              Поскольку операционная система OS/2 была разработана главным
         образом для работы с микропроцессором 80286,  она  работает  и  с
         микропроцессором 80386 в "защищенном режиме 80286".  Следователь-
         но,  операционная система OS/2 поддерживает  одновременно  работу
         только одного блока совместимости,  несмотря на свои мультизадач-
         ные возможности.  Микропроцессор 80386  может  обеспечить  работу
         многих  блоков совместимости в реальном режиме одновременно, если
         микропроцессор работает в "защищенном режиме 80246".  Работа мно-
         жества  блоков совместимости была невозможна под управлением опе-
         рационной системы OS/2 до тех пор,  пока не появилась специальная

                                      - 13-46 -
         версия операционной системы OS/2, ориентированная на микропроцес-
         сор 80386.

                                    Заключение

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




                                ЧАСТЬ V. ПРИЛОЖЕНИЯ



                        Приложение А.   СРЕДСТВА РАЗРАБОТКИ

              Использование командных файлов для автоматизации процес-
              са трансляции с языка Ассемблера
              Использование средства MAKE фирмы "Майкрософт"
              Использование шаблонов  для  создания программ с
              расширением ".COM" и ".EXE"
              Использование библиотечных стандартных программ



              В данном приложении описаны некоторые средства,  которые мо-
         гут упростить и повысить эффективность  использования  языка  Ас-
         семблера для разработки прикладных программ. Ниже приводится опи-
         сание:   автоматизации   процесса    разработки    программы    с
         использованием командных файлов; использование средства MAKE фир-
         мы "Майкрософт"; создание .EXE - и .COM-программ с помощью шабло-
         нов,  а также использование файлов "include" ("файлов включения")
         и библиотечных стандартных программ.

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

              Процессор обработки команд MS-DOS - это часто одно из наиме-
         нее  ценимых  по достоинству средств данной операционной системы.
         Это средство,  однако, может оказаться очень полезным при исполь-
         зовании  Вами  макроассемблера MASM.  В листингах A-1,  A-2 и A-3
         приводятся  исходные  тексты  программ  трех  командных   файлов:
         MASM2EXE.BAT, MASM2COM.BAT и MК.BAT.
              Командный файл MASM2EXE.BAT используется  для  автоматизации
         процесса  трансляции  с  языка ассемблера и редактирования связей
         .EXE - программ.
              Командный файл  MASM2COM.BAT  является  модификацией первого
         файла и включает процесс  преобразования  .EXE-  файлов  в
         .COM-файлы.
              Оба командных файла предназначены для работы с  макроассемб-
         лером  MASM  фирмы "Майкрософт" версий с 1.00 по 4.00 и большинс-
         твом версий редактора связей LINK.
              Значительно более  мощный  командный  файл разработки MК.BAT
         (листинг A-3) предназначен для  работы  с  макроассемблером  MASM
         версии  5.0  и  выше и редактором связей LINK версии 3.00 и выше.
         Командный файл MК.BAT может  использоваться  для  создания  .EXE,
         -.COM - или .ОВJ (объектных программ,  пригодных для редактирова-
         ния связей) - файлов с помощью заданных параметров строки команд.

             Использование командных файлов для макроассемблера  MASM
                                версий с 1.00 по 5

              Командные файлы MASM2EXE.BAT и  MASM2COM.BAT,  показанные  в
         листингах A-1 и A-2,  пригодны для использования с макроассембле-
         ром MASM версий с 1.00 по 4.00 и редактором связей LINK  версий с

                                      - П-2 -
         1.00 по 2.00.
              Модификации командных файлов для более поздних версий макро-
         ассемблера  MASM и редактора связей LINK приводятся в примечаниях
         к листингам.  Оба командных файла требуют наличия второго  файла,
         называемого AUTOLINK.  Этот файл содержит четыре команды возврата
         каретки и перевода строки и используются  для  решения  проблемы,
         касающейся редактора связей LINK (вплоть до версии 2.00): послед-
         ний не может воспринимать  пустые  (неопределенные)  параметры  в
         строке команды для опций распечатки (.МАР) и библиотек (.LIB).
              Инициирование файла AUTOLINK для редактора связей LINK  осу-
         ществляется путем добавления имени файла в строке команды с пред-
         шествующим ему знаком @.  Знак "@" используется для совместимости
         версий  2.00  операционной системы MS-DOS с предыдущими версиями,
         поскольку более  ранние  версии  не  поддерживают  переназначение
         строки команд.
              Использовать конкретный командный файл очень  просто. Просто
         введите  имя данного командного файла с последующим именем файла,
         подлежащего трансляции.  Не включайте расширение,  в то время как
         расширение .ASM допускается.  Если вы используете версию 2.00 или
         выше операционной системы MS-DOS,  и путь доступа PATH установлен
         корректно, то драйверы, на которых хранятся связанные с ними фай-
         лы, не должны указываться.

                             Листинг A-1. MASM2EXE.BAT
         ----------------------------------------------------------------
              echo  off
              if not exist %1.asm goto NOFILLER
              rem
              masm %1 %1 nul nul
              rem
              rem Используйте вышеприведенные строки только с версиями
              rem MASM ниже 2.00
              rem Используйте "masm %1.asm,,;" для макроассемблера MASM
              rem версии 2.00 и выше
              rem
              link %1 @a:autolink
              rem
              rem  Используйте вышеприведенные строки только с версиями
              rem  редактора связей LINK ниже 2.20;
              rem  Используйте "link %1.obj,,nul;" для редактора связей
              rem  LINK версии 2.20 и выше
              rem
              echo Удаление %1.obj
              del %1.obj >nul:
              echo Выполнено!
              dir %1.*
              goto END
              rem
              :NOFILERR
              echo Файл %1.asm не был найден.
              :END
         ----------------------------------------------------------------



                                      - П-3 -
                             Листинг A-2. MASM2COM.BAT
         ----------------------------------------------------------------
              echo off
              if not exist %1.asm goto NOFILERR
              rem
              masm %1 %1 nul nul
              rem
              rem Используйте вышеприведенные строки только с версиями
              rem MASM ниже 2.00
              rem Используйте "masm %1.asm,,;" для макроассемблера MASM
              rem версии 2.00 и выше
              rem
              link %1 @a:autolink
              rem
              rem  Используйте вышеприведенные строки только с версиями
              rem  редактора связей LINK ниже 2.20;
              rem  Используйте "link %1.obj,,nul;" для редактора связей
              rem  LINK версии 2.20 и выше
              rem
              echo Удаление %1.obj
              del %1.obj >nul:
              echo Создание %1.com из %1.exe (и удаление %1.exe)
              exe2bin %1.exe %1.com >nul:
              del %1.exe >nul:
              echo Выполнено!
              dir %1.*
              goto End
              rem
              :NOFILERR
              echo Файл %1.asm не был найден.
              :END
         ----------------------------------------------------------------

              Отметим, что некоторые строки в листинге A-1 и A-2  заканчи-
         ваются параметрами переназначения вывода. Если вы используете бо-
         лее раннюю  версию  по  сравнению  с  2.00  операционной  системы
         MS-DOS, то эти параметры должны быть удалены.

             Использование  командных файлов для макроассемблера MASM
                                  версий 5 и выше

              Если вы  используете макроассемблер MASM версии 5 и редактор
         связей LINK версии 3 или выше,  можно использовать командный файл
         МК.BAT,  показанный  в листинге A-3.  Командный файл МК.BAT может
         быть использован для создания или .EXE,  -.COM, - или .OBJ - фай-
         лов путем указания корректного параметра в строке команды.
              К примеру, если вы хотите оттранслировать с языка ассемблера
         файл,  называемый TEST1.ASM.  для создания файла TEST1.EXE просто
         введите "МК TEST1.EXE".  Наоборот,  если  целевым  является  файл
         TEST1.COM,  введите  "MK  TEST1.COM" или введите "МК ТEST1.OBJ" с
         целью создания .ОВJ файлов объектных программ,  пригодных для ре-
         дактирования связей.
              Командный файл MK.BAT, кроме того, использует коды возврата,
         генерируемые макроассемблером MASM и редактором связей LINK . Ес-
         ли код возврата, отличный от "0", возвращается или макроассембле-
         ром  MASM  или  редактором связей LINK,  то командный файл МК.BAT
         приостанавливает обработку и выдает сообщение об ошибке.

                                      - П-4 -
              Обнаружение ошибки в командном файле МК.BAT полезно,  к при-
         меру, в случае обнаружения ошибки макроассемблером MASM в процес-
         се  трансляции  с  языка ассемблера,  в результате чего командный
         файл МК.BAT лишается продолжения процесса  редактирования  связей
         до тех пор,  пока не будет зафиксирована ошибка в исходном файле.
                                Листинг A-3. MK.BAT
         ----------------------------------------------------------------
      @ECHO off
      REM Используйте вышеприведенную строку для предотвращения вывода
      REM на экран строки "ECHO off", но только в случае работы  с
      REM версией 3.3 и выше операционной системы DOS. В других случаях
      REM используйте  следующие две строки с загруженным ANSI.SYS
      REM ("^[" = Символ EScape (переход) в коде ASCII*).
      REM Выключить режим вывода на экран исполняемых команд
      REM (ECHO off)
      REM ECHO ^[[s^[[1A^[[K^[[u
      REM
         IF (%1)==() goto : NOPARM
         IF not exist %1.asm goto :NOFILE
         SET F1=%1
         IF (%2)==() : ASKTYPE
         SET TYPE=%2
         IF (%3)==() goto :CHKTYPE
         SET MASMS=
         SET LINKS=
         IF (%3)==(m) SET MASMS=%4
         IF (%3)==(M) SET MASMS=%4
         IF (%3)==(mo) SET MASM=%4
         IF (%3)==(mO) SET MASM=%4
         IF (%3)==(Mo) SET MASM=%4
         IF (%3)==(MO) SET MASM=%4
       rem
         IF (%3)==(l)  SET LINKS=%4
         IF (%3)==(L)  SET LINKS=%4
         IF (%3)==(lo)  SET LINK=%4
         IF (%3)==(lO)  SET LINK=%4
         IF (%3)==(Lo)  SET LINK=%4
         IF (%3)==(LO)  SET LINK=%4
       rem
         IF (%5)==(m) SET MASMS=%6
         IF (%5)==(M) SET MASMS=%6
         IF (%5)==(mo) SET MASM=%6
         IF (%5)==(mO) SET MASM=%6
         IF (%5)==(Mo) SET MASM=%6
         IF (%5)==(MO) SET MASM=%6
       rem
         IF (%5)==(l) SET LINKS=%6
         IF (%5)==(L) SET LINKS=%6
         IF (%5)==(lo) SET LINK=%6
         IF (%3)==(lO) SET LINK=%6
         IF (%3)==(Lo) SET LINK=%6
         IF (%3)==(LO) SET LINK=%6
       rem
         GOTO :CHKTYPE
       :ASKTYPE
         ECHO ^H
         ANSWER Наберите имя файла,  подлежащего созданию: OBJ, COM

                                      - П-5 -
                или EXE и нажмите клавишу ENTER
        ECHO ^H
      :CHKTYPE
        IF (%TYPE%)==(o)   SET TYPE=OBJ
        IF (%TYPE%)==(O)   SET TYPE=OBJ
        IF (%TYPE%)==(ob)  SET TYPE=OBJ
        IF (%TYPE%)==(Ob)  SET TYPE=OBJ
        IF (%TYPE%)==(oB)  SET TYPE=OBJ
        IF (%TYPE%)==(OB)  SET TYPE=OBJ
        IF (%TYPE%)==(obj)  SET TYPE=OBJ
        IF (%TYPE%)==(Obj)  SET TYPE=OBJ
        IF (%TYPE%)==(oBj)  SET TYPE=OBJ
        IF (%TYPE%)==(obJ)  SET TYPE=OBJ
        IF (%TYPE%)==(OBj)  SET TYPE=OBJ
        IF (%TYPE%)==(oBJ)  SET TYPE=OBJ
        IF (%TYPE%)==(OBJ)  goto :DOASM
      rem
        IF (%TYPE%)==(e)   SET TYPE=EXE
        IF (%TYPE%)==(E)   SET TYPE=EXE
        IF (%TYPE%)==(ex)  SET TYPE=EXE
        IF (%TYPE%)==(Ex)  SET TYPE=EXE
        IF (%TYPE%)==(eX)  SET TYPE=EXE
        IF (%TYPE%)==(EX)  SET TYPE=EXE
        IF (%TYPE%)==(exe) SET TYPE=EXE
        IF (%TYPE%)==(Exe) SET TYPE=EXE
        IF (%TYPE%)==(eXe) SET TYPE=EXE
        IF (%TYPE%)==(exE) SET TYPE=EXE
        IF (%TYPE%)==(EXe) SET TYPE=EXE
        IF (%TYPE%)==(eXE) SET TYPE=EXE
        IF (%TYPE%)==(EXE) goto :DOASM
      rem
        IF (%TYPE%)==(c)   SET TYPE=COM
        IF (%TYPE%)==(C)   SET TYPE=COM
        IF (%TYPE%)==(co)  SET TYPE=COM
        IF (%TYPE%)==(Co)  SET TYPE=COM
        IF (%TYPE%)==(cO)  SET TYPE=COM
        IF (%TYPE%)==(CO)  SET TYPE=COM
        IF (%TYPE%)==(com) SET TYPE=COM
        IF (%TYPE%)==(Com) SET TYPE=COM
        IF (%TYPE%)==(cOm) SET TYPE=COM
        IF (%TYPE%)==(coM) SET TYPE=COM
        IF (%TYPE%)==(COm) SET TYPE=COM
        IF (%TYPE%)==(cOM) SET TYPE=COM
        IF (%TYPE%)==(COM) goto :DOASM
      rem
        GOTO :ASKTYPE
      :DOASM
      IF (%MASM%)==() SET MASM=/S/P/V/L%MASM%
      IF not (%MASM%)==() ECHO MASM командная строка переключается в: %MASM%
      ECHO on
      MASM %F1%.asm;
      @ECHO off
      IF errorlevel 1 goto :NOASSEM
      REM
      IF (%TYPE%)==(OBJ) goto :LSTFILES
      IF (%LINK%)==() SET LINK=/I/CP:1%LINKS%
      IF not (%LINK%)==() ECHO Link командная строка переключается в: %LINK%

                                      - П-6 -
      ECHO on
      LINK %F1%.obj,%F1%.exe;
      @ECHO off
      IF errorlevel 1 goto : NOLINK
      IF exist %F1%.obj DEL %F1%.obj >nul:
      REM
        IF (%TYPE%)==(COM) EXE2BIN %F1%.exe %F1%.com >nul:
        IF (%TYPE%)==(COM) goto :DEL_EXE
        GOTO :LSTFILES
      :DEL_EXE
        IF exist %F1%.exe DEL %F1%.exe >nul:
      :LSTFILES
        IF exist %F1%.bak DEL %F1%.bak >nul:
        DIR %F1%.*
        GOTO :END
      :NOPARM
        ECHO Параметр не указан!
        GOTO :END
      :NOFILE
        ECHO Файл "%F1%.ASM" не существует!"
        GOTO :END
      :NOASSEM
        ECHO Ошибка ассемблера при трансляции!
        GOTO :END
      :NOLINK
        ECHO Ошибка при редактировании связей!
      :END
        SET F1=
        SET TYPE=
        SET MASM=
        SET LINK=
        SET MASMS=
        SET LINKS=
         ----------------------------------------------------------------
              Отметим, что командный файл МК.BAT использует некоторые  не-
         печатаемые в коде ASCII символы: ESC (переход) в коде ASCII пока-
         зан, как ^[, а BS (возврат) в коде ASCII показан, как ^H.
              В процессе создания командного файла МК.BAT программа-редак-
         тор и программа  текстовой  обработки,  которые  Вы  используете,
         должны  обеспечить  возможность  вставки  этих  символов в данный
         текст.
              Начало листинга показывает использование команды @  ECHO off
         операционной системы MS-DOS версии 3.3, результатом которой явля-
         ется  выключение  режима вывода на экран исполняемых команд,  при
         этом и сама строка ECHO off на экран выводиться не будет.
              Однако, если  Вы работаете с более ранней версией операцион-
         ной системы MS-DOS,  то можно использовать управляющую последова-
         тельность  стандарта  ANSI,  показанные  в нескольких последующих
         строках листинга.
              Управляющая последовательность стандарта  ANSI  используется
         только  тогда,  когда  драйвер  устройства ANSI.SYS загружается в
         процессе первоначальной загрузки системы.
              Символ возврата  в коде ASСII используется с целью отображе-
         ния операционной системой MS-DOS пустой строки в процессе  выпол-
         нения  данного  файла:  результатом  оператора  "ECHO^H" является

                                      - П-7 -
         отображение пустой строки во всех версиях операционной системы
         MS-DOS.
              Командный файл МК.BAT использует программу общего  пользова-
         ния,  называемую ANSWER.COM. Эта программа используется для отоб-
         ражения приглашения операционной системы MS-DOS,  означающего го-
         товность  операционной  системой к приему команд;  в ответ на это
         приглашение любой вводимый текст назначается переменной окружения
         ANSWER.
              Ввод в ответ на запрос может быть затем проверен в командном
         файле путем использования в операторе %ANSWER%.

                  Использование средства MAKE фирмы "Майкрософт"
              Утилита сопровождения программы фирмы "Майкрософт", называе-
         мая MAKE,  может использоваться с целью  значительного  повышения
         уровня автоматизации разработки программ.
              Утилита MAKE используется для автоматического обновления го-
         тового к выполнению файла всякий раз,  когда вносятся изменения в
         один или более ее исходных объектных файлов; кроме того с помощью
         этой утилиты можно обновлять любой файл всякий раз, когда вносят-
         ся изменения в другие смежные файлы.
              Чтобы использовать  утилиту  MAKE,  необходимо создать "файл
         описаний", содержащий команды для утилиты MAKE по построению дан-
         ного проекта.
              К примеру,  если вы хотите создать с  помощью  утилиты  MAKE
         программу, называемую TEST1.EXE, файл описания утилиты MAKE может
         содержать операторы, показанные в листинге A-4.

                  Листинг A-4. Пример файла описания утилиты MAKE
         ----------------------------------------------------------------
             # Стандартная строка команды с текстом макроопределений
             f1=test1               # имя файла, подлежащего созданию
             msm=masm /S /P /V /L   # переключатели  (операторы выбора)
                                    # строки команд макроассемблера MASM
             lnk=link /CP:1 /I      # переключатели (операторы выбора)
                                   # строки команд редактора связей LINK
             #
             # Перечень файлов ASM
             $(f1).obj: $(f1).asm
                 $(msm) $(f1).asm;
             #
             $(f1).exe: $(f1).obj
                 $(lnk) $(f1).obj,$(f1).exe;
                 DEL $(f1).obj
         ----------------------------------------------------------------

              Файл описания утилиты MAKE обычно имеет то же имя, что и го-
         товый к выполнению файл,  но без расширения.  Таким образом, если
         готовый к выполнению файл называется ТЕSТ1.EXE,  то файл описания
         утилиты MAKE,  используемый для создания программы ТЕSТ1.EXE, на-
         зывается просто ТЕSТ1.
              Чтобы создать или обновить программу ТЕSТ1.EXE,  просто вве-
         дите "MAKE ТЕSТ1".

              Файл описания утилиты MAKE, показанный в листинге A-4, имеет
         следующие характеристики:

                                      - П-8 -
              - Любой текст, который начинается с символа #, является ком-
                ментарием и игнорируется утилитой MAKE в процессе обработ-
                ки;
              - "Техt="  представляет  макротекст,  который  присваивается
                строке с текстом,  что позволяет отображать этот  текст  в
                символическом виде по всему файлу описания. Пример, приве-
                денный в листинге A-4, показывает, что макротексту присва-
                ивается значение f1 в качестве имени файла программ, кото-
                рый должен обрабатываться.  Далее значение f1 используется
                на  протяжении остального файла описания с целью представ-
                ления данного файла программ в виде $(f1).  Если Вам необ-
                ходимо  изменить имя данного файла программ,  оно меняется
                только в одном месте, где значение f1 впервые присваивает-
                ся;
              - $(f1).OBJ - это имя первого  целевого  файла,  называемого
                "выходным файлом", а $(f1).ASM (который в данном примере в
                "test1.ASM") - это имя исходного файла, называемого "вход-
                ным  файлом".  Входной файл - это файл,  который требуется
                для создания выходного файла. Выходной файл всегда вводит-
                ся  первым,  за ним следует двоеточие,  за которым следует
                один или более входных файлов,  требуемых для создания вы-
                ходного  файла.  Если  выходной файл отсутствует или имеет
                отметку о времени и дате,  более раннюю чем,  связанный  с
                ним входной файл(ы),  то следующая строка в файле описания
                выполняется как команда  операционной  системы  MS-DOS  (в
                данном примере "$(msm)$(f1);",  которая будет оттранслиро-
                вана в "masm /s/p/v/l/test1;".

                     Если данный входной файл не существует,  то  средство
                MAKE  останавливает обработку в этой точке и выводит сооб-
                щение об ошибке;
             -  $(f1).EXE - имя конечного выходного файла,  а  $(f1).OBJ -
                имя требуемого входного файла. И снова, если файл $(f1).EXE
                (который транслируется в "test1.EXE") не существует или если
                он имеет отметку о времени и дате, более раннюю по сравнению
                с файлом $(f1).OBJ, то следующая строка выполняется как ко-
                манда  операционной  системы  MS-DOS $(lnk)$(f1);,  которая
                транслируется в "link/CP:1/I test1; в данном примере).
                     Если файл  $(f1).OBJ не существует,  то средство MAKE
                останавливает обработку и выводит сообщение об ошибке;
              - Файл описания,  может содержать любую команду операционной
                системы MS-DOS.  В конце примера показано,  как по команде
                DEL$(f1).obj   файл  $(f1).obj  (который  транслируется  в
                /test1.obj) должен быть удален, но только если процесс ре-
                дактирования связей был успешным.

              Средство MAKE способно отреагировать на коды ошибок, возвра-
         щаемые командами операционной системы MS-DOS  в  файле  описания.
         Например, если макроассемблер MASM выдал код возврата отличный от
         "0" (в данном примере $(msm) $(f1).asm;), средство MAKE остановит
         обработку  файла описания в этой точке и выведет на экран сообще-
         ние об ошибке.
              Если макроассемблер успешно проводит трансляцию данного фай-
         ла, однако редактор связей LINK возвратил ошибку, то команда уда-
         ления объектного файла (DEL$(f1).obj) не будет обработана.
              Средство MAKE является  отличным  средством  для  разработки

                                      - П-9 -
         проектов  программ,  особенно больших программных проектов.  Файл
         описания средства MAKE может содержать правила для многочисленных
         исходных  файлов,  а также объектных файлов; кроме того, он может
         описать использование нескольких различных ассемблеров,  компиля-
         торов,  редакторов связей и других средств,  а также и нескольких
         примеров с одинаковыми средствами.

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


            Использование шаблонов для создания .COM- и .EXE - программ

              Листинги A-5,  A-6 и A-7 могут оказаться полезными в началь-
         ной стадии разработки Вами программ.
              Листинг A-5 показывает формат для .EXE-программ с  промежут-
         ками для записи Вашего текста программы.
              Листинг A-6 показывает формат для .COM- файлов.
              Листинг A-7 содержит некоторые макрокоманды, которые могут
         оказаться полезными при написании или .EXE-,  или .COM  -  прог-
         рамм.  Некоторые макрокоманды могут быть или встроены в исходный
         файл Вашей программы,  или могут постоянно размещаться в отдель-
         ном  файле,  который "включается" в Ваш исходный файл в процессе
         трансляции с языка ассемблера (путем встраивания управляющей ко-
         манды INCLUDE макроассемблера MASM в исходный файл).


                        Листинг A-5. Шаблон .EXE-программы
         ----------------------------------------------------------------
            PAGE    60,132             ; широкая печать
          ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
            FALSE   EQU     0          ; сравнить с логическим значением
                                       ; FALSE ("ложь")
            TRUE    EQU     0FFFFH     ; сравнить и проверить по маске
                                       ; логическоe значениe TRUE
                                       ; ("истина")
            ;
            ;    <Установление равенств и макрокоманд>
            ;
            ;- - - - Инициализация - - - - - - - - - - - -
            _TEXT   SEGMENT WORD PUBLIC 'CODE' ;сегмент текста программы
            _TEXT   ENDS
            _DATA   SEGMENT WORD PUBLIC 'DATA' ; сегмент данных
            _DATA   ENDS
            _STACK  SEGMENT PARA STACK 'STACK'  ; сегмент стека
            _STACK  ENDS
            ;
            DGROUP  GROUP   _DATA, STACK
            ;
                    ASSUME cs:_TEXT,ds:DGROUP,ss:DGROUP,es:DGROUP
            ;
            _TEXT   SEGMENT     ; начало сегмента текста программы
            ; Локальная память данных (хранит  эти  описания  в

                                      - П-10 -
            ; сегменте текста программы)
            DSdsave            dw   seg DGROUP  ; память для DS-регистра
            ;
            _TEXT   ENDS
            _DATA   SEGMENT
            PSPseg           dw   ?              ; сегмент PSP
            _DATA   ENDS

         _TEXT   SEGMENT
         ;
         main    PROC     NEAR          ; начинайте процесс "main"
                 mov      ds,DSsave  ; инициализируйте память данных DS
                 mov      ax,es               ; получите адрес сегмента
                 mov      word ptr PSPseg,ax  ; PSP и сохраните его
         ;
         ;
         ;       < Основная стандартная программа >
         ;
         ;
                 mov      ax,4C00h           ; завершите  программу
                 int      21h
         ;
         main    ENDP
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;
         ;   <Оставшаяся часть ваших стандартных программ>
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         _TEXT   ENDS
         _DATA   SEGMENT
         ;
         ;       < Bставьте данные >
         ;
         _DATA   ENDS
         _TEXT   SEGMENT
         ;------------------------------------------------------
         _TEXT   ENDS
                 END      main
         ----------------------------------------------------------------



                        Листинг A-6. Шаблон .COM-программы
         ----------------------------------------------------------------
         PAGE    60,132             ; широкая  печать
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         FALSE   EQU     0          ; сравнить с логическим значением
                                    ; FALSE ("ложь")
         TRUE    EQU     0FFFFH     ; сравнить и проверить по маске
                                    ; логическоe значениe TRUE
                                    ; ("истина")
         ;
         ;    <Установление равенств и макрокоманд>
         ;
         ;- - - - Инициализация - - - - - - - - - - - -
         code   SEGMENT

                                      - П-11 -

         ASSUME  cs:code, ds:code, ss:code, es:code
         ;
         main   PROC  NEAR
         ;
         entry: ORG   0100h
         ;
                mov   sp,offset top_of_stack   ; установите новый стек
         ;
         ;
         ;       < Основная стандартная программа >
         ;
                mov      ax,4C00h               ; завершите программу
                int      21h
         ;
         main   ENDP
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;
         ;   <Оставшаяся часть ваших стандартных программ>
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;
         ;       < Bставьте данные >
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;Дополнительный  стек  -  BHИМAHИЕ - вы ДОЛЖНЫ(!)
         ;использовать функцию 4Сh для завершения данной
         ;программы, если вы используете локальный стек !
         ;
                db       32 DUP ('stack   ')     ; 256-байтный стек
         top_of_stack    EQU    $
         ;
         ;- - - - - - - - - - - - - - - - - - - - - -  - - - - -
         code   ENDS               ; конец сегмента текста программы
                END      entry
         ----------------------------------------------------------------


                 Листинг A-7. Полезные  макрокоманды (STDMAC.INC):
         ----------------------------------------------------------------
         ; - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;  Файл включения макроопределений
         ;- -  - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; Стандартные равенства:
         ;
         TRUE   EQU      0FFFFh  ;  "истина"

            FALSE   EQU     0    ;  "ложь"
            ;
            ; Cтандартные непечатаемые символы
            NUL     EQU    00000000b ; нуль
            BEL     EQU    00000111b ; "звонок"
            BS      EQU    00001000b ; возврат на символ с его стиранием
            HT      EQU    00001001b ; горизонтальная табуляция
            LF      EQU    00001010b ; перевод строки

                                      - П-12 -
            FF      EQU    00001100b ; перевод страницы
            CR      EQU    00001101b ; возврат каретки
            SUBST   EQU    00011010b ; подстановка
            ESCAPE  EQU    00011011b ; переход
            SPACE   EQU    00100000b ; пробел
            COLON   EQU    00111010b ; двоеточие
            SCOLON  EQU    00111011b ; точка с запятой
            ;
            ; Pасширенные символы (фирмы "ИБМ"):
            SLINE   EQU    11000100b  ; горизонтальная строка
            ;
            ;- - - - - - - - - - - - - - - - - - - - - - -
            ..XLIST            ; запретить распечатку макроопределений
            ;;.LALL  _          .  ; распечатка всего
            ;;
            ;;
            ;;** @MODEL ********* Макроопределения общего назначения **
            ;; Установка сегментов согласно модели памяти.
            ;; Это  макроопределение эмулирует управляющую команду
            ;; 5.Х.МОDEL макроассемблера MASM для использования с
            ;; более ранними версиями MASM.

            IF1    ;; Ассемблирование только в процессе прохода 1
            @Model MACRO memory_model,code_name,stack_size
                    ;; ПРИМЕЧAHИЕ: "имя-программы" используется только
                    ;; со средними, большими, а также с очень большими
                    ;; моделями памяти
                    IFNB<memory_model>   ;; модель памяти была указана?
                    ;;
                       IF memory_model  EQ 0
                       @TinyModel stack_size
                   ELSE
                       IF memory_model EQ 1
                       @SmallModel stack_size
                    ELSE
                         IF memory_model EQ 2
                         @MediumModel code_name,stack_size
                       ELSE
                         IF memory_model EQ 3
                         @CompactModel stack_size
                       ELSE
                         IF memory-model EQ 4
                         @ LargeModel code_name,stack_size
                       ELSE
                         IF memory_model EQ 5
                         @Large_Model code_name,stack_size
                       ELSE
                         .ERR
                         %0UT @Model macro: неизвестная модель памяти
                       ENDIF;; конец проверки очень большой модели
                     ENDIF;; конец проверки большой модели
                   ENDIF;; конец проверки компактной модели
                 ENDIF;; конец проверки средней модели
               ENDIF;; конец проверки малой модели
             ENDIF;; конец проверки  очень малой модели
            ;;
            ELSE   ;; модель памяти не была указана

                                      - П-13 -
               .ERR       ;; завершить сообщением об ошибке
               %0UT @MODEL macro error: модель памяти не указана
            ENDIF  ;; конец проверки параметров модели - памяти
            ;;
            ENDM   ;; конец макроопределения
            ;;
            ENDIF  ;; конец обработки  прохода
            ;;
            ;;** @Tiny ************ Макрокоманды общего назначения **
            ;; (Эта управляющая макрокоманда вызывается через @Model 0.
            ;; Эта управляющая макрокоманда также может  вызываться
            ;; непосредственно).
            ;; Отметим, что эта макрокоманда, в отличие от других мак-
            ;; рокоманд  памяти-модели,  не  использует   макрокоманду
            ;; @Stack, поскольку другие стеки в .COM - программах дол-
            ;; жны  быть описаны в конце данной программы. Для опреде-
            ;; ления других стеков в .COM-программах выполните  макро-
            ;; команду @Stack в соответствующем месте исходного текста
            ;; программы.
            IF1   ;; Ассемблировать только в процессе прохода 1
            @TinyModel MACRO
                MEMODEL = 0
               _TEXT SEGMENT BYTE PUBLIC  'CODE' ; сегмент программы
               _TEXT ENDS
               ;; Назначить  физические  сегменты
               ASSUME cs:_TEXT,ds:_TEXT,ss:_TEXT, es:_TEXT
               ;;- - - - - - - - - - - - - - - - - - - - - - -
               ;; Вставьте вручную следующий текст программы после
               ;; @Model 0:
               ;;
               ;; _TEXT SEGMENT
               ;; main PROС near ;;  entry:  ORG 0100h ;;  jmp Start ;;  ;
               вставьте данные  здесь,  если  требуется  ;;  start:  ;;  ;
               вставьте текст программы здесь ;;  main ENDP ;;  ; вставьте
               стандартные программы здесь ;;  ;  вставьте  дополнительный
               стек здесь ;; ; вставьте данные в конце, если требуется
               ;; _TEXT ENDS ;;
               ;;- - - - - - - - - - - - - - - - - - - - - - - - - - -
               ENDM          ;; конец макроопределения
         ENDIF             ;; конец обработки прохода
         ;;** @Small ************ Mакрокоманды общего назначения **
         ;; Управляющая  макрокоманда  для установки Малой (SMALL)
         ;; модели памяти.
         ;; (Эта макрокоманда вызывается  через "@Model Small".
         ;; Эта  макрокоманда может быть вызвана также
         ;; непосредственно.)
         IF1   ;; Ассемблирование только в процессе прохода 1
         @SmallModel MACRO stack_size
               MEMODEL = 1
               _TEXT SEGMENT BYTE PUBLICK 'CODE'  ; сегмент программы
               _TEXT ENDS
               _DATA SEGMENT WORD PUBLIC 'DATA'   ; сегмент данных
                                                  ; (DGROUP)
               _DATA ENDS
               CONST SEGMENT WORD PUBLIC 'CONST'  ; сегмент констант
               CONST ENDS                         ; (DGROUP)
               _BSS SEGMENT WORD PUBLIC 'BSS'     ; сегмент непроинициа-

                                      - П-14 -
               _BSS ENDS                          ; лизированных данных
                                                  ; (DGROUP)
               STACK SEGMENT PARA STACK 'STACK'   ; сегмент стека
                                                  ; (DGROUP)
               STACK ENDS
               ;;
               IFNB <stack_size>
                       @Stack stack_size
               ;;
              ENDIF  ;; конец обработки прохода
               ;;
               DGROUP GROUP _DATA,CONST,_BSS,STACK ; группирование
                                                   ; сегментов данных
               ;;
               ;; Назначить физические сегменты:
               ASSUME   cs:_TEXT, ds:DGROUP, ss:DGROUP, es:DGROUP
               ;;
               ENDM  ;;  конец макроопределения
         ENDIF   ;; конец обработки прохода
         ;;
         ;;** @Medium ******** Макрокоманды общего назначения **
         ;; Управляющая макрокоманда для установки средней модели
         ;; памяти. (Эта макрокоманда  вызывается  через  "@Model
         ;; medium".  Эта  макрокоманда  может быть вызвана также
         ;; непосредственно.)
         IF1  ;; Ассемблировать только в процессе прохода 1
         @MediumModel MACRO code_name,stack_size
                MEMODEL = 2
                code_name_TEXT SEGMENT BYTE PUBLIC 'CODE' ;поименованный
                                                     ; сегмент программы
                code_name_TEXT ENDS                       ;
                _DATA SEGMENT WORD PUBLIC 'DATA'     ; сегмент данных
                _DATA ENDS                           ; (DGROUP)
                CONST SEGMENT WORD PUBLIC 'CONST'    ; сегмент констант
                CONST ENDS                           ; (DGROUP)
                _BSS SEGMENT WORD PUBLIC 'BSS'       ; сегмент непроинициа-
                _BSS ENDS                            ; лизированных данных
                                                     ; (DGROUP)
                STACK SEGMENT PARA STACK 'STACK'     ; сегмент стека
                STACK ENDS                           ; (DGROUP)
                ;;
                IFNB <stack_size>
                        @Stack stack_size
                ENDIF
                ;;
                DGROUP GROUP _DATA,CONST,_BSS,STACK  ; группирование
                                                     ; сегмента данных
                ;;
                ;; Hазначить физические сегменты
                ASSUME  cs:_TEXT, ds:DGROUP, ss:DGROUP, es:DGROUP
                ;;
                ENDM   ;; конец макроопределения
              ENDIF  ;; конец обработки прохода
                ;;
                ;;** @COMPACT ****** Mакрокоманды общего назначения **
                ;; Управляющая макрокоманда для установки компактной
                ;; (COMPACT) модели памяти. (Эта макрокоманда вызывается

                                      - П-15 -
                ;; через "@Model compact". Эта макрокоманда может быть
                ;; вызвана также непосредственно.)
                IF1   ;; Ассемблировать только в процессе прохода 1
                @CompactModel MACRO stack_size
                MEMODEL = 3
              _TEXT SEGMENT BYTE PUBLIC 'CODE' ;сегмент текста программы
              _TEXT ENDS
              FAR_DATA SEGMENT PARA 'FAR_DATA' ; личный далекий сегмент
              FAR_DATA ENDS                    ; данных (DGROUP)
              FAR_BSS SEGMENT PARA 'FAR_BSS'   ; личный далекий непро-
              FAR_BSS ENDS                     ; инициализированный сег-
                                               ; мент данных (DGROUP)
             _DATA SEGMENT WORD PUBLICK 'DATA'  ; сегмент данных
             _DATA ENDS                         ; (DGROUP)
             CONST SEGMENT WORD PUBLIBC 'CONST' ; сегмент констант
             CONST ENDS                         ; (DGROUP)
            _BSS SEGMENT WORD PUBLIC 'BSS'      ; сегмент непроинициа-
            _BSS ENDS                           ; лизированных данных
                                                ; (DGROUP)
              STACK SEGMENT PARA STACK 'STACK'     ; сегмент стека
              STACK ENDS                           ; (DGROUP)
              ;;
              IFNB <stack_size>
                      @Stack stack_size
              ENDIF
              ;;
              DGROUP GROUP _DATA,CONST,_BSS,STACK  ; группирование
                                                   ; сегментов данных
              ;;
              ;; Назначить физические сегменты:
              ASSUME cs:_TEXT,ds:DGROUP, ss:DGROUP,es:DGROUP
              ;;
              ENDM  ;; конец макроопределения
         ENDIF  ;;  конец обработки прохода
         ;;
         ;;** @Large ***************** Mакрокоманды общего назначения **
         ;; Управляющая макрокоманда для установки большой (LARGE)
         ;; модели памяти. (Эта макрокоманда вызывается
         ;; через "@Model Large". Эта макрокоманда может быть
         ;; вызвана также непосредственно.)
         IF1  ;; Ассемблировать только в процессе прохода 1
         @LargeModel MACRO code_name,stack_size
             MEMODEL = 4
             code_name_TEXT SEGMENT BYTE PUBLIC 'CODE'  ; поименованный
             code_name_TEXT ENDS                    ; сегмент программы
             FAR_DATA SEGMENT PARA 'FAR_DATA'    ; личный далекий сегмент
             FAR_DATA ENDS                       ; данных (DGROUP)
             FAR_BSS SEGMENT PARA 'FAR_BSS'      ; личный далекий непро-
             FAR_BSS ENDS                        ; инициализированный сег-
                                                 ; мент данных (DGROUP)
             _DATA SEGMENT WORD PUBLIC 'DATA'    ; сегмент данных
             _DATA ENDS                          ; (DGROUP)
             CONST SEGMENT WORD PUBLIC 'CONST'   ; сегмент констант
             CONST ENDS                          ; (DGROUP)
             _BSS SEGMENT WORD PUBLIC 'BSS'      ; сегмент непроинициа-
             _BSS ENDS                           ; лизированных данных
                                                 ; (DGROUP)

                                      - П-16 -
             STACK SEGMENT PARA STACK 'STACK'    ; сегмент стека
             STACK ENDS                          ; (DGROUP)
             ;;
             IFNB <stak_size>


         @Stack stack_size
              ENDIF
              ;;
              DGROUP GROUP _DATA,CONST,_BSS,STACK  ; группирование
                                                   ; сегмента данных
              ;;
              ;; Назначить физические сегменты
              ASSUME cs:_TEXT,ds:DGROUP, ss:DGROUP,es:DGROUP
              ;;
                ENDM   ;; конец макроопределения
            ENDIF  ;; конец обработки прохода
         ;;
         ;;** @HUGE ************* Mакрокоманды общего назначения **
         ;;  Управляющая макрокоманда для установки очень большой
         ;;  (HUGE) модели памяти. (Эта макрокоманда вызывается
         ;;  через "@Model huge". Эта макрокоманда может быть
         ;;  вызвана также непосредственно.)
         ;;  Oчень  большая (HUGE) модель памяти в настоящее время
         ;;  устанавливается так же как и Большая (LARGE) модель
         ;;  памяти.
         IF1 ;; Ассемблировать только в процессе прохода 1
         @HugeModel MACRO code_name,stack_size
                 MEMODAL = 5
                 @LargeModel code_name,stack_size
                ENDM  ;; конец макроопределения
         ENDIF  ;;  конец обработки прохода
         ;;
         ;;** @Stack ************* Mакрокоманды общего назначения **
         ;; Управляющая  макрокоманда для установки размера стека
         IF1   ;; Ассемблировать только в процессе прохода 1
         @Stack MACRO stack_size,prog_type
           ;;
           IFB <PROG_TYPE>  ;;  если параметр типа программы пустой
             IF MEMODEL EQ 0
               PROGTYPE = 0
             ELSE
               IF MEMODEL EQ 1
                  PROGTYPE = 1
             ELSE
               IF MEMODEL EQ 2
                  PROGTYPE = 1
             ELSE
               IF MEMODEL EQ 4
                  PROGTYPE = 1
               ELSE
                   IF MEMODEL EQ 5
                      PROGTYPE = 1
                   ELSE
                       .ERR
                       %OUT @Stack macro: модель памяти
                       %OUT или тип программы не были установлены

                                      - П-17 -
                   ENDIF;; конец проверки очень большой модели
                 ENDIF ;; конец проверки большой модели
               ENDIF ;; конец проверки компактной модели
             ENDIF ;; конец проверки средней модели
           ENDIF ;; конец проверки малой модели
         ENDIF ;; конец проверки очень маленькой модели
         ELSE ;;  параметр типа программы был указан
           IF prog_type EQ 0  ;; установка программы типа .COM
              PROGTYPE = 0
           ELSE
             IF prog_type EQ 1
              PROGTYPE = 1
             ELSE
               .ERR     ;;  выход с сообщением об ошибке
               %OUT @Stack macro: указан некорректный тип программы
              ENDIF ;; конец проверки типа .EXE
            ENDIF ;; конец проверки типа  .COM
         ENDIF ;;  конец проверки параметра "тип-программы"
         ;;
         IFNB <stack_size>
         ;;
            IF PROGTYPE EQ 0
                  ;  Дополнительный стек. BHИМAHИЕ - вы должны ис-
                  ;  пользовать функцию 4h для  завершения  данной
                  ;  программы, если вы используете локальный стек.
                         db    stack_size DUP ('стек    ')
                  top_of_stack EQU     $
            ELSE ;;  тип программы - .EXE
                  STACK SEGMENT
                          db   stack_size DUP ('стек    ')
                  STACK ENDS
            ENDIF ;; конец проверки PROGTYPE
         ;;
         ELSE  ;; параметр "размер-стека" не был указан
         ;;
           IF PROGTYPE EQ 0
                  ; Дополнительный стек. ВНИМАНИЕ - вы должны исполь-
                  ; зовать функцию 4h для завершения данной программы
                  ; в  случае использования локального стека !
                          db       32 DUP ('стек    ')
              top_of_stack       EQU    $
           ELSE ;; тип программы - .EXE
              STACK SEGMENT
              db     32 DUP ('стек    ')
                       STACK ENDS
                 ENDIF ;; конец проверки PROGTYPE
              ENDIF  ;; конец проверки "размера стека"
              ;;
              ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @SwapNewStack ******** Mакрокоманды общего назначения **
         ;; Переключить стек на новый стек
         IF1   ;; Ассемблировать только в процессе прохода 1
         @SwapNewStack MACRO  tos
              LOCAL        bypass
              ;;

                                      - П-18 -
              jmp          bypass       ;; пропустить область данных
         old_stk_seg       dw      ?         ;; пространство для
                           ;; сегмента стека вызывающей макрокоманды
         old_stk_ptr       dw      ?         ;; пространство для
                          ; указателя стека вызывающей макрокоманды
         new_stk_seg       dw      ?         ;; пространство  для
                                        ;; нового сегмента   стека
         new_stk_ptr       dw  offset tos    ;; пространство  для
                                        ;; нового указателя  стека
                 ;;
         bypass:
                 mov       cs:new_stk_seg,cs ;; установить сегмент
                                             ;; нового стека
                 mov       cs:old_stk_seg,ss ;; сохранить старые
                                             ;; значения стека
                 mov       cs:old_stk_ptr,sp ;; сохранить старый
                                             ;; указатель стека
                 mov       ss,cs:new_stk_seg ;; получить новые
                                             ;; значения стека
                 mov       sp,cs:new_stk_ptr ;; получить новый
                                             ;; указатель стека
                 @PushAll                    ;; сохранить признаки и
                                        ;; содержимое всех регистров
              ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;**@SwapOldStack********** Mакрокоманды общего назначения **
         ;; Переключить с нового стека на исходный стек
                 @Popall               ;; восстановить признаки и
                                       ;; содержимое всех регистров
                 mov       ss,cs:old_stk_seg  ;; восстановить старые
                                              ;; значения стека
                 mov       sp,cs:old_stk_ptr
              ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @DosCall ************* Mакрокоманды общего назначения **
         ;; Bызвать функцию MS-DOS
            IF1     ;;  Ассемблировать только в процессе прохода 1
            @DosCall MACRO
            int      21h
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;**@DirConChar 10 ***** Mакрокоманды общего назначения **
            @DirConCharIO MACRO       ; проверить состояние клавиатуры
                                      ; и считать
                    push     dx              ; cохранить DX
                    mov      dl,0FFh         ; нет символа для вывода
                    mov      ah,06h          ;
              @DosCall
              pop      dx                ; восстановить DX
              ENDM
            ;;
            ;;** @ReadCon_NoEcho **** Mакрокоманды общего назначения **
            @ReadCon_NoEcho MACRO
                    mov     ah,08h     ; считать с клавиатуры без эхо

                                      - П-19 -
                    @DosCall
                    ENDM
            ;;
            ;;** @ReadBuffInput ***** Mакрокоманды общего назначения **
            @ReadBuffInput MACRO bufname   ; считать буферизованный
                                           ; ввод с клавиатуры
                    mov    dx,offset bufname
                    mov    ah,0Ah
                    @DosCall
                    ENDM
            ;;
            ;;**DisChr ************** Mакрокоманды общего назначения **
            ;; Отобразить на экране символ
            IF1    ;; Ассемблировать только в процессе прохода 1
                    @DisChr MACRO   char
                    IFNB<char>      ;; аргумент символа был указан ?
                                    ;; да, поэтому вставить команду
                    push    ax      ;; сохранить используемые регистры
                    push    dx
                    mov     dl,char ;; загрузить символ
                    mov     ah,02h  ;; загрузить  номер функции
                    @DosCall        ;; вызвать MS-DOS
                    pop     dx      ;; восстановить содержимое регистров
                    pop     ax
                    ELSE            ;; в противном случае
                    .ERR            ;; сгенерировать  и вывести
                                    ;; сообщение об ошибке
                    %OUT @DisChr macro : "char" аргумент
                                                     не указывается
                      ENDIF
                    ;;
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода

            ;;
            ;;** @DisStr ************** Mакрокоманды общего назначения **
            ;; Oтобразить на экране строку памяти с  терминатором  кон-
            ;; ца-строки по умолчанию "$" или с указанным терминатором.
            ;; (Bызовы внутренних  макрокоманд @DisStr1 или @DisStr2.)
            IF1  ;; Ассемблировать только в процессе прохода 1
            @DisStr  MACRO  строка,терминатор
                  IFNB <string>    ;; параметр строки был указан?
                                  ;; да, поэтому
                   IFB <terminator>;; терминатор был указан?
                        ;; нет, поэтому вставить команду по
                        ;; умолчанию для  терминатора  "$"
                        @DisStr1 string
                  ELSE  ;;  в противном случае терминатор был указан
                        @DisStr2 string,terminator
                  ENDIF ;; конeц проверки "терминатора"
                  ELSE  ;; в противном случае,"строка" не была указана
                  .ERR  ;; сгенерировать и вывести сообщение об ошибке
                  %OUT @DisStr macro: "string" аргумент не
                                                           указывается
               ENDIF
               ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода

                                      - П-20 -
            ;;
            ;;** @DisStr1 ********** Служебные макрокоманды *******
            ;; Вызваны макрокомандой @DisStr1 для отображения строки
            ;; памяти с терминатором конца-строки "$" по умолчанию.
            IF1    ;; Ассемблировать только в процессе прохода 1
            @DisStr1 MACRO  string
                     push   ax    ;; сохранить используемые регистры
                     push   dx
                     mov    dx,offset ds:string ;; указать на  строку
                                                ;; в памяти
                     mov    ah,09h          ;; загрузить номер функции
                     @DosCall               ;; обратиться к  MS-DOS
                     pop    dx              ;; сохранить регистры
                     pop    ax
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;** @DisStr2 ************ Служебные макрокоманды *******
            ;; Вызваны макрокомандой @DisStr1 для отображения строки
            ;; памяти с указанным терминатором конца-строки.
            IF1      ;; Ассемблировать только в процессе прохода 1
            @DisStr2 MACRO string,terminator
                     LOCAL strloop,strloopdone ;; создать локальные метки
                     push  si                  ;; сохранить регистры
                     push  ax
                     push  bx
                     push  dx
                     xor   bh,bh               ;; очистить BX
                     mov   bl,terminator       ;; получить терминатор
                     mov   si,offset string    ;; указать  на строку
                     xor   dx,dx
            strloop:
                     mov   dl,byte ptr [si] ;; получить следующий символ
                     cmp   dl,bl               ;; это терминатор?
                     je    strloopdone         ;; да, мы сделали
                     mov   ax,02h              ;; загрузить функцию
                                               ;; вывода символов
                     @DosCall                  ;; и вызвать DOS
                     jnc   si           ;; указать на следующий символ
                     jmp   short strloop       ;; и повторить
            strloopdone:
                     pop   dx      ;; восстановить содержимое регистров
                     pop   bx
                     pop   ax
                     pop   si
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;** @TypeStr ********** Mакрокоманды общего назначения **
            ;; Отобразить ближайшую строку.
            ;; Примечание: "строка" должна быть представлена в кавычках
            ;; с тем, чтобы она обрабатывалась как один аргумент макро-
            ;; команды  и  чтобы гарантировать, что данные закодированы
            ;; корректно.
            IF1      ;; Ассемблировать только в процессе прохода 1
            @TypeStr MACRO   string         ;; определить и отобразить
                                            ;; строку
                     LOCAL   TypeStrAddr    ;; установить локальную метку

                                      - П-21 -
                     ;;
                     IF MEMODEL NE 0        ;; если не тип .COM
            _TEXT    ENDS                   ;; конец сегмента программы
            _DATA    SEGMENT              ;; изменить на сегмент данных
                     ENDIF
            TypeStrAddr  DB string,'$' ;; определить строку в сегменте
                                       ;; данных
                     IF MEMODEL NE 0   ;; если не тип .COM
            _DATA    ENDS              ;; конец сегмента данных
            _TEXT    SEGMENT        ;; вернуться к сегменту программы
                     ENDIF
                     ;;
                     @DisStr TypeStrAddr   ;; отобразить строку
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;** @TypeStrCR ******** Mакрокоманды общего назначения **
            ;; Oтобразить ближайшую строку , завершаемую с
            ;; помощью GR/LF.
            IF1      ;; Ассемблировать только в процессе прохода 1
            ;; "строка" должна быть представлена в кавычках с тем, чтобы
            ;; она обрабатывалась как один аргумент макрокоманды.
            @TypeStrCR MACRO    string
                     @TypeStr   string ;; определить и отобразить строку
                     @NewLine          ;; завершить с помощью GR/LF
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;**@NewLine *********** Mакрокоманды общего назначения **
            ;; Oтобразить символ возврата каретки и перевода строки.
            IF1      ;; Ассемблировать только в процессе прохода 1
            @NewLine MACRO
                     IFNDEF     EXT_NEWLINE     ;; символ EXT_NEWLINE
                                                ;; был определен?
                     EXTRN      newLine:NEAR    ;; нет, вставьте EXTRN
                                                ;; только один раз
                     EXT_NEWLINE EQU 0          ;; и опишите равенство
                                                ;; только один раз
                     ENDIF        ;; верхние две строки не должны
                         ;; в последующие вызовы макрокоманд вставляться
                     call newLine ;; вызвать процедуру NEWLINE
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;**@DisNum ************ Mакрокоманды общего назначения **
            ;; Отобразить двоичное число в десятичном или шестнадцати-
            ;; ричном виде кода ASCII.
            IF1      ;; Ассемблировать только в процессе прохода 1
            @DisNum MACRO number,type,digits,sign
                     ;;
                     ;; Проверьте сначала требуемые параметры
                     IFB <number>  ;; числовой параметр указан?
                     .ERR          ;; нет, завершить обработку
                                   ;; сообщением об ошибке
                     %OUT @DisNum macro: "number" параметр не указан.
                     ENDIF
                     ;;

                                      - П-22 -
                     IFNB <type>   ;; тип вывода указан?
                     ;;
                   If type EQ 10         ;; десятичное  преобразование
                                         ;; указано?
                     IFNDEF EXT_BIN2DEC  ;; было определеноEXT_BIN2DEC?
                     EXTRN bin2dec:NEAR  ;; нет, вставьте определение
                                         ;; EXTRN
                  EXT_BIN2DEC EQU 0    ;; и только раз установите
                                       ;; равенство
                  ENDIF
                  ;;
                  ELSE
                  ;;
                IF type EQ 16                ;; шестнадцатиричное
                                        ;; преобразование  указано?
                  IFNDEF EXT_BIN2HEX    ;; было определено EXT_BIN2HEX?
                  EXTRN   bin2hex:NEAR  ;; нет, вставьте определение
                  EXT_BIN2HEX EQU 0     ;; EXTRN и только раз установите
                                        ;; равенство
                  ENDIF
                ;;
                ELSE
                ;;
                 .ERR
                 %OUT @DisNum Macro: указан "type" запрещенный  "тип"
                 ENDIF        ;; окончить  проверку  для шестнадца-
                              ;; тиричного   преобразования
             ;;
             ENDIF ;; окончить  проверку  для десятичного преобразования
         ;;
         ELSE   ;; в противном  случае,  параметр пустой
         ;;
         .ЕRR   ;; завершить выполнение сообщением об ошибке
         %OUT @DisNum macro: "type" параметр не указан
         ENDIF  ;;  окончить  проверку  пустого параметра
         ;; Oкoнчить проверку требуемых параметров
         ;;
         ;; Hачать вставку программы
         push     ax            ;; сохранить содержимое регистров
         push     cx
         push     dx
         ;;
         mov ax,number          ;; поместить число в AX
         ;;
         IFNB <digits>          ;; аргумент "разряды"  указан?
         mov      ch,digits     ;; да, записать значение в CH
         ELSE                   ;; в противном случае,  по  умолчанию
         mov      ch,1          ;; отобразить по крайней мере 1 разряд
         ENDIF
         ;;
         IFNB <sign>            ;; аргумент "знак"  указан?
         mov      dx,sign       ;; да, поэтому поместить его в DX
         ELSE                   ;; в противном случае, по умолчанию знак
         mov      dx,0          ;; будет  отсутствовать
         ENDIF
         ;;
         If type  EQ 10         ;; десятичное преобразование указано?

                                      - П-23 -
         call     bin2dec
         ELSE
                     IF type EQ 16  ;; шестнадцатиричное преобразование
                                    ;; указано?
                             call bin2hex
                             ENDIF  ;; конец проверки по основанию 16
                     ENDIF   ;; конец проверки по основанию 10
                     ;;
                     pop     dx   ;; восстановить содержимое регистров
                     pop     cx
                     pop     ax
                     ;;
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;
            ;;** @GetDate ********** Mакрокоманды общего назначения **
            ;; Получить  системную дату
            IF1      ;; Ассемблировать только в процессе прохода 1
            @GetDate MACRO
                     mov      ah,2Ah ;; загрузить номер функции
                     @DosCall        ;; вызвать  MS-DOS
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
           ;;** @GetTime ********** Mакрокоманды общего назначения **
            ;; Получить  системное время
            IF1      ;; Ассемблировать только в процессе прохода 1
            @GetTime MACRO
                     mov      ah,2Ch ;; загрузить номер функции
                     @DosCall        ;; вызвать  MS-DOS
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;** @DiskRead ******** Mакрокоманды общего назначения **
            ;; Считать логический  сектор(а)
            IF1      ;; Ассемблировать только в процессе прохода 1
            @DiskRead MACRO
                     int     25h  ;; обработать абсолютное прерывание
                                  ;; по чтению диска
                  ENDM   ;; конец макроопределения
            ENDIF  ;; конец выполнения прохода
            ;;
            ;;** @DiskWrite ******* Mакрокоманды общего назначения **
            ;; Записать в логический сектор(а)
            IF1      ;;  Ассемблировать только в процессе прохода 1
         @DiskWrite MACRO
                  int      26h     ;; обработать машинное прерывание
                                   ;; по записи на диск
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @GetDOSVersion **** Mакрокоманды общего назначения **
         ;; Получить номер версии операционной  системы MS-DOS
         IF1      ;; Ассемблировать только в процессе прохода 1
         @GetDOSVersion MACRO
                  push     bx      ;; сохранить содержимое

                                      - П-24 -
                                   ;; уничтоженных  регистров
                  push     cx
                  mov      ah,30h  ;; загрузить номер функции
                  @DosCall         ;; вызвать MS-DOS
                  pop      cx      ;; восстановить содержимое регистров
                  pop      bx
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @GetDOSVer ******* Mакрокоманды общего назначения **
         ;; Получить номер версии операционной  системы MS-DOS
         IF1      ;; Ассемблировать только в процессе прохода 1
         @GetDOSVer MACRO
                  IFNDEF   EXT_GDOSV     ;; символ определен?
                  EXTRN    GETDOSV:NEAR  ;; нет, вставить команду EXTRN
                                         ;; только  один  раз
                  EXT_GDOSV EQU 0        ;; и только раз определить
                                         ;; равенство
                  ENDIF                  ;; (две верхние строки не
                                         ;; должны вставляться в
                                         ;; последующие вызовы
                                         ;; макроопределения)
                  call     dosver        ;; вызвать библиотечную
                                         ;; стандартную программу
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @DisDOSVer ********* Mакрокоманды общего назначения **
         ;; Получить и отобразить номер версии DOS
         IF1      ;; Ассемблировать только в процессе прохода 1
         @DisDOSVer MACRO
                  IFNDEF   EXT_DDOSV     ;; символ определен?
                  EXTRN    DOSV2CON:NEAR ;; нет, вставить команду EXTRN
                                         ;; только  один  раз
                  EXT_DDOSV EQU 0        ;; и только раз определить
                                         ;; равенство
                  ENDIF                  ;; (две верхние строки не
                                         ;; должны вставляться в
                                         ;; последующие вызовы
                                         ;; макроопределения)
                  call     dosv2con      ;; вызвать библиотечную
                                         ;; стандартную программу
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @ChangeСase ******* Mакрокоманды общего назначения **
         ;; Изменить регистр  клавиатуры для  символа
         IF1      ;; Bыполнять только в  процессе прохода 1
         @ChangeCase MACRO  char,type
              IFB <char>   ;; символ, подлежащий преобразованию,
                           ;; указан ?
              .ERR                ;; нет, сгенерировать и
                                  ;; вывести сообщение  об  ошибке
              %OUT @ChangeCase macro: "char" параметр не определен !
              ELSE                   ;; в противном случае
              mov      al,char       ;; загрузите символ в AL
              ENDIF

                                      - П-25 -
              ;;
              IFB <type>          ;; тип преобразования указан?
              mov   ah,0          ;; нет, поэтому загрузить 0 в AH
              ELSE
              mov   ah,type   ;; загрузить тип преобразования в AH
              ENDIF
              ;;
              IFNDEF EXT_CHGCASE  ;; символ EXT_CHGCASE определен?
              EXTRN  CHGCASE:NEAR ;; нет, вставить EXTRN только раз
              EXT_CHGCASE EQU 0   ;; и только раз определить равенство
              ENDIF    ;; (две верхние строки не должны вставляться в
                       ;; последующие вызовы данной макрокоманды)
              ;;
              call     chgcase    ;; вызвать библиотечную процедуру
                                  ;; изменения регистра клавиатуры
           ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @case ************* Mакрокоманды общего назначения **
         ;; Mакрокоманда CASE языка ассемблера
         @Case    MACRO    key,case_list,jmp_labels
                  ??tmp_1 = 0
                  IRP      match,<&case_list>  ;; упорядочить регистры
                    ??tmp_1 = ??tmp_1 + 1  ;; установить номер индекса
                    cmp    key,&&match       ;; регистр соответствует?
                    ??tmp_2 = 0
                    IRP    retl,<&jmp_labels>  ;; упорядочить переходы
                      ??tmp_2 = ??tmp_2 + 1  ;; до тех пор, пока индекс
                                             ;; не будет соответствовать
                      IF (??tmp_1 EQ ??tmp_2)
                        je &&&retl             ;; да !
                        EXITM
                      ENDIF   ;; конец проверки условия
                    ENDM      ;; конец второго блока IRP
                  ENDM        ;; конец первого блока IRP
                  ENDM        ;; конец макроопределения
         ;;
         ;;
         ;;*****************************************************
         ;; Используйте макрокоманды @PushAll и @PopAll вместо ко-
         ;; манд   PUSHA   и   POPA,   поддерживаемых  процессором
         ;; 80186/80188/80286/80386 для обеспечения  совместимости
         ;; с процессорами 8086/8088.
         ;;
         ;;** @PushAll ********** Mакрокоманды общего назначения **
         ;;  Поместить в стек содержимого всех регистров
         IF1     ;; Выполнять только в процессе прохода 1
         @PushAll MACRО  ;; сохранить содержимое всех регистров в стеке
                 push      ax
                 push      bx
                 push      cx
                 push      dx
                 push      dp
                 push      di
                 push      si
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода

                                      - П-26 -
         ;;
         ;;** @PopAll *********** Mакрокоманды общего назначения **
         ;; Снять со стека содержимое всех регистров
         IF1     ;; Выполнять только в процессе прохода 1
         @PopAll MACRO           ;; восстановить  содержимое всех
                                 ;; регистров из стеке
                 pop       si
                 pop       di
                 pop       dp
                 pop       dx
                 pop       cx
                 pop       bx
                 pop       ax
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;** @ExiToDos ********* Mакрокоманды общего назначения **
         ;;   Завершить процесс с необязательными установками
         ;;   значений переменной ERRORLEVEL
         IF1     ;; Выполнять только в процессе прохода 1
         @ExiToDOS MACRO errorcode
                 IFB <errorcode>    ;; код ошибки был указан?
                 mov       ax,4C00h ;; нет, загрузить функцию и значение
                                    ;; переменной ERRORLEVEL = "0" в AX
                 ELSE               ;; в противном случае
                 mov       ah,4Ch   ;; загрузить функцию и значение
                 mov       al,errorcode  ;; переменной ERRORLEVEL отдельно
                 ENDIF
                 ;;
                 @DosCall           ;; вызвать MS-DOS
               ENDM   ;; конец макроопределения
         ENDIF  ;; конец выполнения прохода
         ;;
         ;;********************************************************
         ;; Конец макроопределений
         ;;********************************************************
         .LIST      ; восстановить выдачу листингов в нормальном виде
         ;; Kонец файла включения макроопределений

         ----------------------------------------------------------------



              Использование стандартных библиотечных программ

              Если вы  используете  стандартный  набор немодифицированных
         программ во всех Ваших программах,  для Вас может оказаться  по-
         лезным  поместить эти стандартные программы в библиотечный файл,
         который всегда связан с Вашими программами.  Использование этого
         метода упрощает процесс трансляции с ассемблера и редактирования
         связей,  а также уменьшает размер исходных файлов вашей програм-
         мы. Библиотечный файл создается путем трансляции файла, содержа-
         щего Ваши стандартные программы, и последующей обработки .OBJ  -
         файла с помощью LIB - программы,  помещенной на диске с макроас-
         семблером MASM.  В результате обработки LIB-программы получается
         файл, содержащий корректно отформатированную объектную программу
         с расширением .LIB.  Внешние ссылки на эти стандартные программы

                                      - П-27 -
         должны  быть описаны в исходном тексте данной программы, которая
         должна вызывать эти стандартные программы. Эти стандартные прог-
         раммы пишутся в формате:
              EXTRN стандартная программа : расстояние
              где EXTRN - управляющая команда,  которая сообщает макроас-
         семблеру MASM,  что "стандартная подпрограмма"  будет включаться
         во время редактирования связей либо из объектного файла, либо из
         библиотечного файла.
              Параметр "расстояние"  имеет  значение или "близко" (near),
         или "далеко" (far) в зависимости от того, как была описана стан-
         дартная программа,  на которую осуществляется ссылка.  Для прог-
         рамм с расширением типа .COM параметр и расстояние  всегда имеют
         значение "близко".
              После того как внешние стандартные  программы  описаны,  их
         можно вызывать также,  как и любые другие стандартные программы.
              В листинге A-8 представлен полный исходный текст библиотеч-
         ного  файла  STDLIB.LIB,  о  котором рассказывалось в предыдущих
         главах.


                  Листинг A-8. Исходный текст библиотечного файла
                                    STDLIB.LIB
         ----------------------------------------------------------------
         PAGE 60,132
         TITLE   stdlib.asm/.ob Ўlib
         .8086       ; позволяет только команды процессоров 8086/8088
         .SALL       ; запретить распечатку макрорасширений
         ;- -  - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;
         ;- - - - - - Равенства и макроопределения - - - - - - - -
         ;
         INCLUDE stdmac.inc ; включить стандартные макробиблиотеки
                            ; и равенства
         ;
         ;- - - - - - - - - - - Инициализация - - - - - - - - - -
         ;
         ; Инициализация, описанная ниже, представляет собой подм-
         ; ножество (и совместимое) с управляющей командой ".МОDEL
         ; SMALL" макроассемблера MASM версии 5.0 и выше.
         ;
         _TEXT   SEGMENT WORD PUBLIC 'CODE'   ; сегмент программы
         _TEXT   ENDS
         ;
         _DATA   SEGMENT WORD PUBLIC 'DATA'   ; сегмент данных
         _DATA   ENDS
         ;
         DGROUP  GROUP   _DATA       ; определить группу сегментов
         ;
         ASSUME  cs:_TEXT,ds:_DATA   ; назначить  физические сегменты
         ;
         ;
         ;********************************************************
         ; Начало библиотечных стандартных программ
         ;********************************************************
         ;
         _TEXT   SEGMENT            ; начало сегмента текста программы
         ;

                                      - П-28 -
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; Новая строка : отображает новую строку (возврат каретки +
         ;                + перевод строки)
         ;
         ; Вход : нет
         ;
         ; Выход :  AX и DX восстанавливаются;
         ;          другие регистры не используются.
         ;
         ; Bызываемых стандартных программ: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - -  - -
         PUBLIC NEWLINE          ; библиотечная  стандартная  программа
         ;
         newline PROC    NEAR
                 push    ax           ; сохранить содержимое регистров
                 push    dx
         ;
                 mov     dl,CR         ; отобразить возврат каретки
                 mov     ah,02h
                 @DosCall
                 mov     dl,LF         ; отобразить перевод строки
                 mov     ah,02h
                 @DosCall
         ;;
                 pop     dx   ; восстановить содержимое регистров
                 pop     ax
                 ret
         ;
         newline ENDP
         ;
         ;
         ;++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ;   CSAVE :  Выполняет автоматическое сохранение и
         ;   восстановление  содержимого  регистров : BX, CX, DI и
         ;   SI внутри вызываемой стандартной программы. Она вызы-
         ;   вается из  другой  вызываемой  стандартной  программы
         ;   следующим образом :
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ; LOCALIZE       EQU   10h
         ; routine PROC   NEAR  ; FAR, если средняя, большая
         ;                      ; или очень большая модель
         ;
         ;      push      bp
         ;      mov       bp,sp
         ;      sub       sp,LOCALIZE
         ;      call      csave
         ;      :
         ;      (текст стандартной программы)
         ;      :
         ;      ret          ; всегда направляется в $cret
         ;
         ; routine ENDP
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         ;
         ; Вход : смотри описание выше
         ;

                                      - П-29 -
         ; Выход : смотри описание выше
         ;
         ; Используемые регистры: содержимое регистров BX, CX, DI и
         ;                        SI сохраняется; регистры AX и DX
         ;                        не трогаются.
         ;
         ; Вызываемые стандартные программы: идут обращения к
         ; "вызывающей" стандартной программе до тех пор, пока
         ; по ее команде возврата RET не осуществится возврат в
         ; эту программу, после чего по команде возврата RET
         ; этой программе осуществится возврат в исходную
         ; вызвавшую программу.
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC   CSAVE       ; библиотечная стандартная программа
         ;
         csave   PROC      NEAR
                 push     bp     ; установить адресацию стека
                 mov      bp,sp
                 xchg     bx,[bp+2] ; сохранить содержимое регистров
                                    ; BX и получить адрес возврата
                                    ; вызвавшей стандартной программы
                  pop       bp  ; восстановить текущий адрес возврата
                  push      cx  ; сохранить содержимое остальных
                                ; регистров
                  push      si
                  push      di
                  call      bx     ; продолжить обработку в
                                   ; вызвавшей стандартной программе
         ;
         ; Перейти  сюда по команде возврата RET вызвавшей
         ; стандартной программы
         $cret:   pop       di       ; восстановить сохраненное
                                     ; содержимое регистров
                  pop       si
                  pop       sx
                  pop       bx
                  mov       sp,bp    ; cбросить локальные переменные
                  pop       bp
                  ret                ; aвозвращает в то место, куда
                                   ; обычно идет возврат по команде
                               ; возврата RET в вызывающую программу
         ;
         csave    ENDP
         ;
         ;
         ;++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; BIN2DEC -   Преобразование из двоичной в десятичную сис-
         ; тему  счисления. Отображает на экране дисплея в десятич-
         ; ном виде шестнадцатиразрядное число со  знаком  или  без
         ; знака. Находит самый последний разряд справа путем деле-
         ; ния.  Повторяет  до  тех пор, пока все не будет найдено.
         ; Может быть указано минимальное количество разрядов, под-
         ; лежащих отображению. Если минимальное количество указан-
         ; ных разрядов больше  фактического  количества  разрядов,
         ; результирующее число дополняется начальными нулями.
         ;

                                      - П-30 -
         ; Вход :   AX = число, подлежащее отображению
         ;          CH = минимальное число разрядов, подлежащих
         ;                отображению
         ;          DX = 0, если число, подлежащее обработке, не
         ;                  имеет знака или = 1, если число, под-
         ;                  лежащее обработке, имеет знак
         ;
         ; Выход : (регистры AX, CХ и DX восстанавливаются)
         ;
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC   BIN2DEC      ; библиотечная стандартная программа
         ;
         bin2dec  PROC    NEAR
           push    ax              ; сохранить содержимое регистров
           push    bx
           push    cx
           push    dx
           mov     cl,0            ; очистить счетчик разрядов
           mov     bx,10           ; установить делитель = 10
           cmp     dx,0            ; всегда  отображать номер, как
                                   ; положительное число?
           je      more_dec        ; да, игнорировать проверку на
                                   ; отрицательное число
         ;
         ; Проверить на  отрицательное число. Если число
         ; отрицательное, сделать его положительным.
            or       ax,ax       ; число положительное ?
            jnl     more_dec     ; да, игнорировать "отрицательное"
            neg     ax           ; сделать число положительным
            @DisChr '-'          ; отобразить знак "минус"
         ;
         ; Основной цикл деления - получить десятичный разряд.
         ; Повторять, пока остаются разряды.
         more_dec:
                  xor     dx,dx                ; очистить
                  div     bx                   ; разделить на 10
                  push    dx                   ; сохранить остаток
                  inc     cl          ; добавить к значению счетчика
                                      ; разрядов единицу
                  or      ax,ax       ; проверить  частное
                  jnz     more_dec    ; продолжить, если больше
         ;
         ; Основной цикл печати разрядов - обратный порядок.
                  sub     ch,cl       ; минимальное число разрядов
                                      ; получено?
                  jle     morechr     ; да, начать отображение
                  xor     dx,dx       ; нет, начать вставлять "нули"
         morezero:
                  push    dx
                  inc     cl          ; добавить к значению счетчика
                                      ; разрядов единицу
                  dec     ch          ; проверить на совпадение
                  jnz     morezero    ; нет - продолжать вставку
         morechr:
                  pop     dx          ; восстановить последний разряд
                  add     dl,30h      ; преобразовать в код ASCII

                                      - П-31 -
                  @DisChr dl          ; разряд результата
                  dec     cl          ; вычесть из счетчика разрядов
                                      ; единицу
                  jnz     morechr     ; продолжить,  если больше
         ;
                  pop     dx    ; восстановить содержимое регистров
                  pop     cx
                  pop     bx
                  pop     ax
                  ret

         ;
         bin2dec  ENDP
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++
         ; BIN2DEC2 - Преобразование из двоичной в десятичную систе-
         ;   му  счисления;  отображает  32-разрядное число. Создает
         ;   два десятичных числа, которые отображаются через  обра-
         ;   щения к BIN2DEC.
         ;       Может быть указано минимальное число разрядов, под-
         ;   лежащих  отображению.  Если минимальное количество ука-
         ;   занных разрядов больше фактического числа разрядов, вы-
         ;   ходное число дополняется начальными нулями.
         ;       Примечание:  сдвоенных регистров, содержащих число,
         ;   разбивается в лучшем случае, наименьшая значащая  часть
         ;   числа составляет 9.999. Оно никогда не будет отображено
         ;   как отрицательное число.
         ;
         ; Вход :   DX: AX = число, подлежащее отображению
         ;          CH = минимальное число разрядов, подлежащих
         ;                отображению
         ;
         ; Выход : (регистры AX : DX восстанавливаются)
         ;
         ; Вызываемые стандартные программы: BIN2DEC (вывод
         ;            16-разрядного числа в десятичном виде)
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC  BIN2DEC2    ; библиотечная стандартная  программа
         ;
         bin2dec2 PROC    NEAR
         ;EXTRN  bin2dec2:NEAR   ; ссылка на стандартную программу
                                 ; BIN2DEC
                  push    ax     ; сохранить содержимое регистров
                  push    bx
                  push    cx
                  push    dx
         ;
         ;   Проверить на отрицательное число.  Если  отрицательное,
         ; сделать положительным.
                  or      dx,dx       ; число положительное ?
                  jnl     bd2_pos  ; да, игнорировать "отрицательное"
                  not     ax          ; сделать число положительным
                  not     dx
                  add     ax,1        ; (точное) дополнение в двоич-
                                      ; ной системе счисления (допол-
                                      ; нительный код), реализуемое

                                      - П-32 -
                                      ; аппаратным способом
                  adc     dx,0
                  push    dx
                  push    ax
                  @DisChr '-'
                  pop     ax
                  pop     dx
         ;
         ; Теперь разбить число, подлежащее распечатке, на две
         ; управляемые части.
         bd2_pos:
                  mov     bx,10000    ; установить делитель = 10000
                  div     bx          ; разбить число  на пары
                  cmp     dx,0        ; отбросить, если наибольшее
                  je      bd2_2big    ; значащее число слишком велико
                  or      ax,ax       ; выяснить, равно ли "0"
                                      ; наибольшее значащее число
                  jz      bd2_nosig   ; нет наибольшего значащего
                                      ; числа
         ;
         ;  Печать первого наибольшего значащего числа (старшая
         ;  часть числа).
               push    dx
               sub     ch,4        ; четыре разряда будут распе-
                                   ; чатаны из наименьшей значащей
                                   ; части  числа
               jnc     bd2_cntok   ; запросить больше 4 для того,
                                   ; чтобы значение счетчика стало
                                   ; действительным
               mov     ch,0        ; в противном  случае, опрос идет
                              ; до тех пор, пока не кончатся разряды
         bd2_cntok:

                  call    bin2dec     ; печатать наибольшую значащую
                                      ; часть числа
                  pop     dx          ; восстановить наименьшую
                                      ; значащую часть числа
                  mov     ch,4        ; четыре разряда в наименьшей
                                      ; значащей части числа
         ;
         ; Печатать наименьшую значащую часть числа (младшую часть
         ; числа).
         bd2_nosig:
                  mov     ax,dx       ; печатать сначала содержимое
                                      ; регистра DX (наименьшую зна-
                                      ; чащую  часть числа)
                  call    bin2dec     ; печатать наименьшую значащую
                                      ; часть числа
         bd2_done:
                  pop     dx          ; восстановить содержимое регист-
                                      ; ров и выйти из программы
                  pop     cx
                  pop     bx
                  pop     ax
                  ret
         bd2_2big:
                  @DisStr  Bin2BigErrMsg

                                      - П-33 -
                  jmp     short bd2_done   ; возвратиться из стан-
                                           ; дартной программы
         ;
         _TEXT    ENDS
         _DATA    SEGMENT
         Bin2BigErrMsg db "BIN2DEC2 error: Oшибка: число слишком
                                           велико.$"
         _DATA    ENDS
         _TEXT    SEGMENT
         ;
         bin2dec2 ENDP
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; BIN2HEX  -  Преобразование из двоичной в  шестнадцатирич-
         ; ную  систему  счисления;  отображает 16-разрядное число в
         ; шестнадцатиричном виде.
         ;       Может быть указано минимальное количество разрядов,
         ; подлежащих отображению : если минимальное число указанных
         ; разрядов  больше фактического количества разрядов, выход-
         ; ное число дополняется значащими нулями.
         ;
         ; Вход :   AX = число, подлежащее отображению
         ;          CH = минимальное число разрядов, подлежащих
         ;               отображению (от 1 до  4).
         ;               Если CH=0, то значение счетчика разря-
         ;               дов по умолчанию принимается равным 4)
         ;
         ; Выход : нет (регистры AX и CX восстанавливаются)
         ;
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC BIN2HEX    ; библиотечная стандартная программа
         ;
         bin2hex  PROC     NEAR
                  push     ax   ; сохранить содержимое регистров
                  push     bx
                  push     cx
                  push     dx
         ;
             mov      bx,ax        ; использовать регистр BX для
                                   ; временного хранения
             cmp      ch,0         ; счетчик уже установлен?
             jne      align_left   ; да, тогда продолжать
             mov      ch,4         ; иначе, установить значение
                                   ; счетчика символов, равным 4
         ;
         ; Выровнять число по левому краю регистра AX (циклически
         ; сдвигать влево на (4 - CH) х 4 битовые позиции
         allign_left:
               mov      cl,4   ; определить число сдвигаемых цифр
               sub      cl,ch
               shl      cl,1   ; умножить на 4
               shl      cl,1
               rol      bx,cl        ; выравнивание влево
               mov      cl,4         ; и установить наименьшее
                        ; значение счетчика циклического сдвига

                                      - П-34 -
         ;
         ; Основной  цикл повторить  N раз. Печатать старший разряд.
         more_hex:
                  rol      bx,cl        ; переместить на один разряд
                                        ; вправо
                  mov      al,bl        ; переслать в регистр AL
                  and      al,0Fh       ; только младший разряд
                  add      al,90h       ; скрытое преобразование в
                  daa                   ; шестнадцатиричные символы
                                        ; в коде ASCII
                  adc      al,40h
                  daa
         ;
         ; Отобразить разряд
                  @DisChr  al
                  dec      ch       ; вычесть из счетчика разрядов
                                    ; единицу
                  jnz      more_hex ; продолжить, если больше
         ;
                  pop      dx   ; восстановить содержимое регистров
                  pop      cx
                  pop      bx
                  pop      ax
                  ret
         ;
         bin2hex ENDP
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; CHGCASE   -    Изменить  регистр  клавиатуры для симво-
         ; лов. Меняет регистр символов в коде ASCII. Указывается
         ; тип преобразования регистра клавиатуры.
         ;
         ;  1. Принудительное изменение верхнего  регистра  клавиатуры
         ;     на нижний.
         ;  2. Принудительное изменение нижнего регистра клавиатуры на
         ;     верхний.
         ;  3. Изменить состояние регистра клавиатуры на противополож-
         ;     ное (если нижний, то верхний; если верхний, то нижний).
         ;
         ; Вход :   AL = буквенный символ в коде ASCII, подлежащий
         ;               преобразованию
         ;          AH = тип преобразования:
         ;               "L" или "l" = печать символа на нижнем
         ;                             регистре клавиатуры
         ;               "U" или "u" = печать символа на верхнем
         ;                             регистре клавиатуры
         ;               любое другое значение меняет состояние
         ;               регистра клавиатуры на противоположное
         ;
         ; Выход :  AL = преобразованный  символ в коде ASCII
         ;          AH = состояние символа:
         ;               "L" = нижний регистр
         ;               "U" = верхний регистр
         ;               0 = если символ в регистре AL не являлся
         ;                   буквенным символом в коде ASCII
         ;               Содержимое всех других регистров

                                      - П-35 -
         ;               восстанавливается
         ;
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC  CHGCASE   ; библиотечная стандартная программа
         ;
         chgcase PROC      NEAR
                 push      dx    ; сохранение содержимого регистров
         ;
         ; Определить, содержит ли регистр AL буквенный символ в коде
         ; ASCII, и если символ действителен, определить его регистр.
             cmp       al,"A"       ; находится символ под первой
                                    ; буквой верхнего регистра?
             jl        error        ; да, он не является буквенным
                                    ; символом в коде ASCII
             cmp       al,"Z"       ; находится символ под последней
                                    ; буквой верхнего  регистра?
             jle       is_upper     ; да, символ соответствует
                                    ; верхнему регистру
             cmp       al,"a"       ; находится символ под первой
                                    ; буквой нижнего регистра?
             jl        error        ; да, он не является буквенным
                                    ; символом  в  коде ASCII
             cmp       al,"z"       ; находится символ под последней
                                    ; буквой нижнего регистра?
             jle       is_lower     ; да, символ находится в нижнем
                                    ; регистре
             jmp       short error  ; в противном случае, он не являет-
                                    ; ся буквенным символом в коде ASCII
         ;
         is_upper:
             mov       dl,"U"        ; пометить символ как признак
                                     ; верхнего регистра
             jmp       short convert_type ; и продолжить
         is_lower:
              mov      dl,"L"        ; пометить символ как признак
                                     ; нижнего регистра
              jmp      short convert_type ; и продолжить
         error:
              mov      ah,0   ; пометить символ как не буквенный
                              ; символ  в  коде  ASCII
              jmp      short done  ; и завершить выполнение процедуры
         ;
         convert_type:
              cmp      ah,"l"     ; изменить  на  нижний регистр?
              je       to_lower   ; да, поэтому изменить символ
              cmp      ah,"L"     ; изменить  на  нижний регистр?
              je       to_lower   ; да, поэтому изменить символ
              cmp      ah,"u"     ; изменить  на  верхний регистр?
              je       to_upper   ; да, поэтому изменить символ
              cmp      ah,"U"     ; изменить  на  верхний регистр?
              je       to_upper   ; да, поэтому изменить символ
         ;
         ;    В противном случае, изменить регистр клавиатуры
         ;    на противоположный
            cmp      dl,"L"      ; это символ нижнего регистра?
            je       to_upper    ; да, изменить его на верхний регистр

                                      - П-36 -
                            ; в противном случае, это верхний  регистр,
                            ; поэтому сделать его нижним
         ;
         to_lower:
             mov      ah,"L"     ; установить признак регистра для
                                 ; возврата
             cmp      dl,ah      ; символ уже соответствует нижнему
                                 ; регистру?
             je       done       ; да, действие выполнено
             add      al,20h     ; в противном случае, изменить на
                                 ; нижний регистр
             jmp      short done ; и выйти из процедуры
         ;
         to_upper:
             mov      ah,"U"   ; установить признак регистра для возврата
             cmp      dl,ah    ; символ уже соответствует верхнему
                               ; регистру ?
             je       done     ; да, действие выполнено
             sub      al,20h   ; в противном случае, изменить на
                               ; верхний  регистр
         ;
         done:
                  pop      dx  ; восстановить содержимое регистров
                  ret
         ;
         chgcase  ENDP
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; DOSVER -  Получает версию операционной системы MS-DOS
         ; и  возвращает старшую и младшую версии. Возвращает значе-
         ; ние "1.00", если текущая версия DOS 1.00 или 1.10.
         ;
         ; Вход :   нет
         ;
         ; Выход :  AL = старшая версия
         ;          AH = младшая версия ( =  00,  если  перед этим
         ;               была  версия 2.00 DOS)
         ;       (Содержимое всех других регистров восстанавливается)
         ;
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC  DOSVER       ; библиотечная стандартная программа
         ;
         dosver    PROC    NEAR
                   push    bx      ; сохранить содержимое регистров
                   push    cx
                   push    dx
         ;
                   xor     ax,ax             ; очистить регистр AX
                   mov     ah,30h    ; загрузить функцию "получить
                                     ; версию DOS"
                   @DosCall
                   cmp     al,0      ; это версия предыдущая 2.00?
                   jg      dos2plus  ; нет, действие сделано
                   mov     al,1    ; в противном случае это версия 1.ХХ
                   mov     ah,0    ; установить младшую версию в "00"

                                      - П-37 -
         ;
         dos2plus:
                   pop     dx      ; восстановить содержимое регистров
                   pop     cx
                   pop     bx
                   ret
         ;
         dosver    ENDP
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; DOSV2CON: Получает и отображает текущую версию операцион-
         ; ной системы MS-DOS, и возвращает версию  вызывающей  прог-
         ; рамме.
         ;
         ; Вход :   нет
         ;
         ; Выход :  AL = старшая версия
         ;          AH = младшая версия
         ;       (Содержимое всех других регистров восстанавливается)
         ;
         ; Вызываемые стандартные программы:
         ;        DOSVER  (получает версию MS-DOS)
         ;        BIN2CON (отображает номера в десятичном виде)
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC DOSV2CON    ; библиотечная стандартная  программа
         ;
         dosv2con   proc   NEAR
         ;                                    ;
         ;EXTRN  dosver: NEAR    ; получить  версию MS-DOS
         ;EXTRN  bin2dec: NEAR   ; отображает номера в десятичном виде
         ;
                push   bx      ; сохранить  содержимое  регистров
                push   cx      ;
                push   dx      ;
         ;
                call   dosver  ; получить  версию MS-DOS
                push   ax      ; сохранить возвращенную версию
                push   ax      ; и сохранить ее снова
                xor    ah,ah   ; записать номер старшей версии в
                               ; регистр AL
                mov    ch,1    ; отобразить по крайней мере 1 разряд
                call   bin2dec ; вывести  на  печать число
                @DisChr '-'    ; символ-разделитель
                pop    ax      ; восстановить номер младшей версии
                cmp    al,1    ; это версия 1.ХХ?
                je     ver1xx  ; да, отобразить "ХХ" как младшую версию
                xchg   ah,al   ; в противном случае поместить номер
                               ; младшей версии в регистр AL
                xor    ah,ah   ; очистить верхние
                mov    ch,2    ; отобразить, по крайней мере, 2 разряда
                call   bin2dec ; вывести число
                jmp    short end_ver      ; конец
         ;
         ver1xx:
                    @DisChr 'X'   ; вывести "Х"
                    @DisChr 'X'   ; и опять

                                      - П-38 -
         ;
         end_ver:
                    pop      ax   ; восстановить  номер версии для воз-
                                  ; врата основной стандартной программе
                    pop      dx   ; восстановить остальные регистры
                    pop      cx
                    pop      bx
                    ret
         ;
         dosv2con   ENDP           ; конец  стандартной  программы
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; MEMALLOC - Распределяет  блок памяти указанного размера
         ; в параграфах (16 байт).
         ;
         ; Вход :  BX = размер запрашиваемого блока в 16-байтных
         ;              параграфах
         ;
         ; Выход : Признак  переноса  = 0, если УСПЕХ, причем
         ;                 AX = адрес сегмента распределенного
         ;                      блока памяти
         ;                 (Содержимое  регистра BX восстанавливается)
         ;         Признак переноса = 1, если ОТКАЗ,  причем
         ;                 AX = код ошибки,
         ;                      7 = разрушенные блоки управления памятью
         ;                      8 = недостаточная память
         ;                 BX  = наибольший доступный блок памяти в
         ;                       параграфах
         ;
         ; Вызываемые стандартные программы: нет
         ;- -  - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC MEMALLOC   ; библиотечная стандартная  программа
         ;
         memalloc  PROC      NEAR
                   push      bp        ; сохранить указатель базы
                   push      bx        ; сохранить содержимое регистра BX
                   mov       bp,sp     ; инициализировать указатель базы
         ;
                   xor       al,al     ; очистить регистр AL
                   mov       ah,48h    ; загрузить функцию распределения
                                       ; памяти
                   @DosCall            ; выполнить распределение памяти
                   jnc       end_memalloc     ; завершить выполнение,
                                       ; если нет ошибки с адресом
                                       ; сегмента в регистре AX
                                    ; в  противном случае, завершить
                                    ; выполнение с установкой признака
                                    ;  переноса
                   mov       word ptr [bp],bx ; максимальным размером
                                       ; блока  (в  регистре BX)
                                       ; и кодом ошибки в регистре AX
         ;
         end_memalloc:
                   pop       bx       ; восстановить регистр BX
                   pop       bp       ; восстановить указатель базы
                   ret

                                      - П-39 -
         ;
         memalloc ENDP
         ;
         ;
         ;+++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; MEMSIZE:  Изменяет размер блока памяти,  ранее  распреде-
         ; ленной  с помощью стандартной программы MEMALLOC. Указыва-
         ; ются адрес блока и запрашиваемый размер (в 16-байтных  па-
         ; раграфах).
         ;
         ; Вход :  ES = адрес сегмента распределенного блока памяти
         ;         BX = новый размер в 16-байтных параграфах
         ;
         ; Выход : Признак  переноса  = 0, если УСПЕХ, причем
         ;            (Содержимое  всех регистров восстанавливается)
         ;         Признак переноса = 1, если ОТКАЗ,  причем
         ;                 AX = код ошибки,
         ;                      7 = разрушенные блоки управления памятью
         ;                      8 = недостаточная память
         ;                      9 = недействительный адрес блока
         ;                 BX  = наибольший доступный блок памяти в
         ;                       параграфах, если AX = 8,
         ;                       в противном случае, содержимое его
         ;                       восстанавливается.
         ;                (Содержимое регистра ES восстанавливается)
         ;
         ; Вызываемые стандартные программы: нет
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC   MEMSIZE   ; библиотечная стандартная  программа
         ;
         memsize  PROC       NEAR
                  push       bp   ; сохранить указатель базы
                  push       es   ; сохранить адрес блока памяти
                  push       ax   ; сохранить регистр AX
                  push       bx   ; сохранить регистр BX
                  mov        bp,sp  ; инициализировать указатель базы
         ;
                  xor        al,al   ; очистить регистр AL
                  mov        ah,4Ah  ; загрузить функцию и изменить
                                     ; размер блока
                  @DosCall
                  jnc        end_memsize       ; завершить выполнение,
                                               ; если нет ошибки
                            ; в противном случае, завершить выполнение
                            ; с установкой признака  переноса
                  pushf                        ; сохранить признаки
                  cmp        ax,8              ; памяти недостаточно?
                  jne        memsize_err       ; нет, продолжить
                  mov        word ptr [bp],bx  ; в противном случае,
                              ; сохранить максимально доступный размер
         memsize_err:
                  mov        word ptr [bp+2],ax ; сохранить код ошибки
                  popf                          ; восстановить  признаки
         ;
         end_memsize:
                  pop        bx   ; восстановить содержимое регистров
                  pop        ax

                                      - П-40 -
                  pop        es
                  pop        bp   ; восстановить указатель базы
                  ret
         ;
         memsize  ENDP
         ;
         ;
         ;++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; MEMFREE - Освобождает  блок памяти, ранее распределенный
         ; стандартной программой MALLOC.
         ;
         ; Вход :  ES = адрес сегмента распределенного блока памяти
         ;
         ; Выход : Признак  переноса  = 0, если УСПЕХ
         ;                (Содержимое регистра ES восстанавливается)
         ;         Признак переноса = 1, если ОТКАЗ,  причем
         ;                 AX = код ошибки,
         ;                      7 = разрушенные блоки управления памятью
         ;                      9 = недействительный адрес блока
         ;                (Содержимое регистра ES восстанавливается)
         ;
         ; Вызываемые стандартные программы: нет
         ;
         ;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
         PUBLIC MEMFREE     ; библиотечная стандартная  программа
         ;
         memfree  PROC       NEAR
                  push       bp              ; сохранить указатель базы
                  push       es              ; сохранить адрес блока памяти
                  push       ax              ; сохранить регистр AX
                  mov        bp,sp           ; инициализировать указатель
                                             ; базы
                  xor        al,al           ; очистить регистр AL
                  mov        ah,49h          ; загрузить функцию осво-
                                             ; бождения памяти
                  @DosCall             ; выполнить освобождение памяти
                  jnc        end_memfree     ; завершить выполнение,
                                             ; если нет ошибки
                                 ; в противном случае, завершить выпол-
                                 ; нение с установкой признака переноса
                  mov   word ptr [bp],ax ;  и кодом ошибки (регистр AX)
         ;
         end_memfree:
                  pop        ax   ; восстановить содержимое регистра AX
                  pop        es   ; восстановить адрес блока
                  pop        bp   ; восстановить  указатель базы
                  ret
         ;
         memfree  ENDP
         ;
         ;
         ;++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         ; MERRHNDL - Обработка ошибки распределения памяти/ос-
         ; вобождения памяти (повторного задания размера).
         ;
         ; Вход :  AX = код ошибки
         ;         BX  = наибольший доступный блок памяти

                                      - П-41 -
         ;                       (если код ошибки = 8)
         ;         ES = адрес сегмента распределенного блока памяти
         ;                       (если код ошибки = 9)
         ;
         ;         ES = адрес сегмента распределенного блока памяти
         ;
         ; Выход : нет (содержимое всех регистров восстанавливается)
         ;
         ; Вызываемые стандартные программы:
         ;        BIN2CON (отображает номера в десятичном виде)
         ;- - - - - - - - - - - - - - - - - - - - - - - - - -  - -
         PUBLIC  MERRHNDL       ; библиотечная стандартная  программа
         ;
         merrhndl PROC      NEAR
         ;
                 cmp        ax,7   ; очищены блоки управления памятью?
                 jne        mem_error8       ; нет, продолжить проверку
                 @DisStr    TrashedMemErr_Msg; да, завершить выполнение
                                             ; сообщением
                 ret                         ; вернуться
         ;
         mem_error8:
                 cmp        ax,8             ; памяти  недостаточно?
                 jne        mem_error9       ; нет, продолжить проверку
                 @DisStr    InsuffMemErr_Msg ; да, завершить выполнение
                                             ; сообщением
                 @DisNum    bx,10,1,0   ; и наибольшим доступным блоком
                                        ; памяти
                 @Newline               ; отобразить пустую строку
                 ret                    ; вернуться
         ;
         mem_error9:
                 cmp        ax,9             ; адрес блока памяти
                                             ; недействителен?
                 jne        mem_err_unknown  ; нет, неизвестна причина
                 @DisStr    IncorrSegAddr_Msg; отобразить сообщение об
                                             ; ошибке
                 @DisNum    es,16,4      ; отобразить адрес  сегмента
                 @NewLine                ; отобразить пустую строку
                 ret                     ; вернуться
         ;
         mem_err_unknown:
                 @DisStr   UnknownMemErr_Msg ; вывести сообщение
                 ret
         ;
         _TEXT   ENDS            ; конец сегмента текста программы
         _DATA   SEGMENT         ; начало сегмента данных
         trashedMemErr_Msg db  ""Сбой при распределении памяти: управ-"
                           db "ляющие блоки памяти разрушены.",CR,LF,"$"
         InsuffMemerr_Msg  db ""Сбой при  памяти: "
                           db "Недостаточно памяти",CR,LF
                           db "наибольший доступный блока памяти = $"
         IncorrSegAddr_Msg db "Неправильный адрес сегмента для "
                       db "повторного задания размера/освобождения.",CR,LF
                           db "Адрес сегмента = $"
         UnknownMemErr_Msg db "Неизвестная ошибка при распределении/"
                           db "повторном задании размера/освобождении "

                                      - П-42 -
                           db "памяти.",CR,LF,"$"
         _DATA    ENDS            ; конец сегмента данных
         _TEXT    SEGMENT         ; начало сегмента программы
         ;
         merrhndl ENDP
         ;
         ;
         ;********************************************************
         ;  Конец стандартных библиотечных программ
         ;********************************************************
         _ТEXT    ENDS
                  END
         ----------------------------------------------------------------


              Как видно из листинга, все стандартные программы должны быть
         описаны  в  исходном файле как PUBLIC (общего пользования) с тем,
         чтобы сделать их доступными для других программ. Любая метка (ко-
         торая представляет собой то же, что и имя стандартной программы),
         подлежащая использованию в других программах, должна быть описана
         таким образом.
              Если стандартные программы должны включаться  в  .EXE-файлы,
         то единственное что необходимо - это использовать управляющую ко-
         манду EXTRN,  помещенную "за пределами описания сегмента". Редак-
         тор  связей  LINK  находит  данную ссылку в библиотеке и помещает
         стандартную программу,  на которую идет ссылка в  ее  собственный
         сегмент в окончательной программе.  Однако,  если эти стандартные
         программы должны  включаться  в файлы типа .COM,  то оба имени и
         сегмента и "класса",  используемые для .COM - программы,  должны
         соответствовать именам,  используемым в стандартной библиотечной
         программе.
              Чтобы использовать стандартные программы BIN2DEC или BIN2НEХ
         программа  типа  .COM должна использовать следующее описание сег-
         мента:

            code     segment para public "code"

              Отметим, что описание данного сегмента должно быть определе-
         но  как PUBLIC.  В этом случае и имя сегмента (code) и имя класса
         ('code') одинаковы, чтобы облегчить их запоминание. Кроме того уп-
         равляющие команды EXTRN должны быть помещены в описание  сегмента,
         что позволит макроассемблеру MASM знать, какие внешние стандартные
         программы являются составной частью этого сегмента.  (Метки PUBLIC
         и EXTRN даются атрибутам того же сегмента, что и сегмента, который
         включает их описания).
              Дополнительная информация  относительно  библиотечных  стан-
         дартных  программ,  меток PUBLIC и EXTRN можно найти в справочных
         руководствах по макроассемблеру MASM и редактору связей LINK фир-
         мы "Майкрософт".

                                      - П-43 -


                   Приложение Б. НЕ ОПИСАННЫЕ В ДОКУМЕНТАЦИИ ПО
                 ОПЕРАЦИОННОЙ СИСТЕМЕ MS-DOS ПРЕРЫВАНИЯ И ФУНКЦИИ



              Не описанные в  документации прерывания операционной
              системы MS-DOS
              Не описанные в документации вызовы функций прерывания 21h(33)


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


               Не описанные в  документации прерывания операционной
                                  системы MS-DOS

              В версии 3.3 операционной системы 2Eh описаны в документации
         по операционной системе как  зарезервированные. Прерывания  28h,
         29h и 2Eh описаны в следующих параграфах.


                                      - П-44 -

                Прерывание 28h(40): прерывание по безопасности DOS

              Прерывание 28h  обычно  называется "прерыванием для безопас-
         ности операционной системы MS-DOS" или "прерыванием по циклу  за-
         нятости  клавиатуры".  Оно  используется  внутренней  стандартной
         программой операционной системы MS-DOS "Get  Input"  (Осуществить
         ввод с клавиатуры),  если только она безопасна для  использования
         функций  "0Сh"  и  выше при прерывании "int 21h". Прерывание "int
         28h" используется главным образом программами "завершиться и  ос-
         таться  резидентными",  включая  программу PRINT.COM операционной
         системы MS-DOS.  Если какая-либо  программа  вызвала  стандартную
         программу  "Осуществить ввод с клавиатуры" и ожидает нажатия кла-
         виши, операционная система MS-DOS приостанавливает вызов прерыва-
         ния  "int  28h"  в качестве сигнала другим прикладным программам,
         которые могут могут быть загружены,  что никакие функции прерыва-
         ния  "int  21h" (кроме 0Сh и выше) не должны вызываться.  Нажатие
         клавиши и завершение выполнения данной стандартной программы  вы-
         зывает  прерывание "int 28h",  служащее сигналом для других прик-
         ладных программ,  которые могут быть загружены, что система прос-
         таивает  и безопасна для выполнения функций прерывания "int 21h".
              В большинстве случаев прерывания "int 28h" используются сов-
         местно  с  функцией  34h (Получить признак занятости операционной
         системы DOS) прерывания "int 21h" :  операционная система  MS-DOS
         вызывает или снимает прерывание "int 28h" соответственно, а прик-
         ладная программа вызывает функцию установления признака занятости
         DOS с целью определения незанятого состояния системы.


                  Прерывание 29h(41): Вывод на устройство консоли

              Прерывание "int  29h" ,  часто называемое по "выводу на уст-
         ройство консоли" или FAST PUTCHAR,  вызывает внутренние стандарт-
         ные программы вывода операционной системы MS-DOS, если вывод осу-
         ществляется на устройство,  а не в файл,  а также, если  в  слове
         атрибута   драйвера,  обслуживающего  данное  устройство,  разряд
         3(04h) установлен в 1.  Прерывание "int 29h" можно назвать черным
         ходом для драйвера устройства вывода на консоль: символ в регист-
         ре AL выводится на консоль в процессе обработки этого прерывания.

             Прерывания с 2Ah(42) по 2Dh(45) : внутренние стандартные
                       программы операционной системы MS-DOS

              Прерывания, начиная с "int 2Ah" и кончая "int 2Dh" , являют-
         ся  внутреними прерываниями операционной системы MS-DOS.  Векторы
         всех этих прерываний указывают на код операции  IRET.  Прерывание
         "int 2Ah"  используется для управления сетью в системах, установ-
         ленных с MSNET и другим сетевым  программным  обеспечением  фирмы
         "Майкрософт".


            Прерывание 2Eh(46). "Черный ход" для командного процессора

              Обычно для обработки команды с помощью командного процессора
         COMMAND.COM используется  функция  EXEC (функция  4Вh  прерывания

                                      - П-45 -
         "int 21h").  Однако, альтернативный, быстрый и "грубый" метод вы-
         полнения  той же функции обеспечивает прерывание "int 2Eh". Чтобы
         выполнить какую-либо команду операционной системы MS-DOS, вначале
         уплотняется  память  с  целью выделения места для новой программы
         (как и в случае функции 4Вh прерывания "int 21h" ), затем заносят
         в регистр DS:SI указатель на строку параметров данной команды и в
         конце обрабатывается прерывание "int 2Eh". Первый байт  в  строке
         параметров данной команды - это длина строки, за ней следует сама
         строка (например,  CHKDSK  C:),  которая  заканчивается  символом
         "возврат каретки" (0Dh).
              Этот завершающий символ считается частью длины строки. После
         того как прерывание "int 2h" обработано, важным моментом является
         обнуление стека,  поскольку в результате прерывания "int 2h"  со-
         держимое регистров SS и SP может не сохраниться.

                         Прерывания с 30h(48) по FFh(255)

              Для операционной системы MS-DOS, начиная с версии 3.30, пре-
         рывания с "int 30h" по "int FFh" помещаются  как  зарезервирован-
         ные.  Однако  некоторые  из этих прерываний используются дополни-
         тельным аппаратным и программным обеспечением.  К примеру, преры-
         вание  "int  67h"  является зарезервированным для использования в
         спецификации  систем  расширенной  памяти  (EMS)   с   драйверами
         Lotus/Intel/Microsoft (LIM). С помощью этого прерывания (реализо-
         ванного в драйвере устройства EMS)выполняются все функции LIM EMS
         (см.  главу 7 для более полной информации по системам расширенной
         памяти EMS).  Прерывания с 30h(48) по FFh(255) используют  другие
         типы дополнительного аппаратного и программного обеспечения , та-
         кого как сетевые адаптеры фирмы "Майкрософт" и "ИБМ", стандартные
         программы базовой системы ввода-вывода (BIOS), адаптеры EGA и VGA
         и другие.

           Не описанные в документации вызовы функций прерывания 21h(33)

              Следующие вызовы функций  прерывания  "int 21h" помечены как
         "зарезервированные" или "неиспользуемые" в Техническом справочном
         руководстве  по  операционной  системе  DOS  фирм  "Майкрософт" и
         "ИБМ"  (для версии 3.3 операционной системы MS-DOS):  18h,  с 1Dh
         до 20h,  32h,  34h,  37h,  с 50h по 53h, 55h, 58h, 5Dh, 60h, 61h,
         63h, 64h.
              Наибольшим номером  описанной  функции  прерывания "int 21h"
         (по данным на версию  3.3  операционной  системы MS-DOS) является
         68h. Зарезервированные функции свыше 68h,  если  вообще  имеются,
         еще предстоит узнать.  Ниже описаны более детально недокументиро-
         ванные функции прерывания 21h и выполняемые этими функциями  опе-
         рации.

             Функции  18h(24), 1Dh(29), 1Eh(30), 20h(32h): формальные
        функции, обеспечивающие совместимость с операционной системой CP/M
              Многие функции операционной системы MS-DOS c  более  низкими
         номерами эквивалентны   подобным  функциям  операционной  системы
         CP/M.  Не все из функций CP/M реализованы в операционной  системе
         MS-DOS,  однако  многие из них располагают "пустым окном" для об-
         легчения переноса программ операционной системы  CP/M  в  MS-DOS.
         Эти функции после своего выполнения ничего не возвращают.


                                     - П-46 -

                Функция 1Fh(31): найти информацию о блоке на диске
                                для текущего диска

              Функция 1Fh  используется для возврата указателя на "таблицу
         с информацией о блоке на диске" для текущего диска. В таблице Б-1
         приведен формат этой таблицы и ее содержимое.
              Вход :   AH = 1Fh
              Возврат: DS:BX содержит адрес точки первого входа блока на
                       диска для текущего диска
              Примечание: Функция 1Fh аналогична функции 32h,  за исключе-
                       нием того, что функция 32h возвращает информацию  о
                       блоке диска для указанного диска. При работе с вер-
                       сиями 2.0 и выше операционной  системы  MS-DOS  эта
                       функция  просто  выполняет  функцию  32h прерывания
                       "int 21h" при AL = 0.

                                                         Таблица Б-1
                  Формат блока диска операционной системы MS-DOS
         ДДДДДДДДВДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Смещение¦     Тип    ¦Данные
         ДДДДДДДД†ДДДДДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         00      ¦Байт        ¦Диск (дисковод) 0 = A, 1 = В и т.д.
         01      ¦Байт        ¦Устройство внутри драйвера (0,1,2, и т.д.)
         02      ¦Слово       ¦Количество байтов  в  секторе
         04      ¦Байт        ¦Количество секторов в кластере - 1;
         05      ¦Байт        ¦Кластер сдвига сектора
         06      ¦Слово       ¦Количество зарезервированных (начальная
                 ¦            ¦загрузка) секторов
         08      ¦Байт        ¦Количество  таблиц FAT
         09      ¦Слово       ¦Количество элементов корневого каталога
         0B      ¦Слово       ¦Номер сектора  кластера  2  (1-ый  сектор
                 ¦            ¦данных)
         0D      ¦Слово       ¦Количество кластеров +1 (или последнего
                 ¦            ¦кластера)
         0F      ¦Байт        ¦Секторы для таблиц FAT
         10      ¦Слово       ¦Номер сектора в каталоге
         12      Двойное слово¦Адрес  заголовка устройства
         16      ¦Байт        ¦Байт дескриптора среды
         17      ¦Байт        ¦Нуль, если к диску  идет обращение
         18      Двойное слово¦Адрес следующего блока диска операционной
                 ¦            ¦системы DOS (FFFF, если последний блок диска
                 ¦            ¦в цепочке)
         ДДДДДДДДБДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД



                Функция 32h(50) : найти информацию о блоке на диске
                               для указанного диска

              Функция 32h аналогична функции 1Fh, за исключением того, что
         может  быть  определен  указатель  на таблицу информации о блоках
         диска для указанного диска.
               Вход : AH = 32h
                      DL = номер дисковода (0 = по умолчанию, 1= A и т.д.)

                                      - П-47 -
               Возврат: AL = 00 если диск существует; =FFh если нет.
                       DS:BX содержит адрес первого входа в блок диска.
               Примечания: Функция 32h аналогична функции 1Fh, за исключе-
                       нием только того, что функция 1Fh возвращает инфор-
                       мацию о блоке диска для текущего диска

              Функция  34h (52): получить флаг занятости операционной
                                  системы MS-DOS

              Функция 34h возвращает указатель на флаг занятости  операци-
         онной  системы MS-DOS (также называемый флагом критической секции
         операционной системы MS-DOS). Флаг занятости операционной системы
         MS-DOS - это байт,  установленный в "0" в случае его безопасности
         для прерывания операционной системы MS-DOS и установленный в зна-
         чение, не  равное "0" в случае отсутствия безопасности для преры-
         вания операционной системы MS-DOS.  Эта функция используется сов-
         местно  с прерыванием "int 28h":  данное прерывание устанавливает
         флаг занятости операционной системы MS-DOS,  а функция 34h по об-
         работке  прерывания "int 21h" указывает на положение флага (отсы-
         лаем к более раннему обсуждению не описанных в документации  пре-
         рываний).
              Вход:    AH = 34h
              Возврат: ES:BX указывает на флаг занятости MS-DOS
              Примечания: Есть некоторые специфические особенности, касаю-
                       щиеся флага занятости операционной системы MS-DOS в
                       различных версиях операционной системы  MS-DOS. При
                       работе под управлением  операционной системы MS-DOS
                       версии 2.10 байт,  расположенный непосредственно за
                       флагом занятости операционной системы MS-DOS должен
                       быть установлен в "00", чтобы вызвать прерывание по
                       обработке   PRINT.COM.   Для  операционной  системы
                       MS-DOS версии 3.0 и 3.1 (за исключением  COMPAQ DOS
                       3.0) байт  перед флагом занятости операционной сис-
                       темы MS-DOS должен быть установлен в "0",  для опе-
                       рационной  системы COMPAQ DOS версии 3.0 байт 01AAh
                       до этого должен быть установлен в "0".


             Функция 37h(55): получить/установить символ переключения

              Функция 37h используется для изменения символа,  который ис-
         пользуется для переключений командных строк операционной системы
         MS-DOS. "Символ-переключатель"  по  умолчанию  представляет собой
         символ "/" (наклонная черта), однако он может быть изменен с  по-
         мощью функции 37h на какой-нибудь другой символ, подобный символу
         переноса (-),  который является по умолчанию подобным символом  в
         операционной системе UNIX. Эта функция была полно описана в доку-
         ментации по операционной системе MS-DOS до того, как была выпуще-
         на версия 3.0 операционной системы MS-DOS, когда в составе ее ко-
         манд  была  команда,  которая  должна  была  помещаться  в   файл
         конфигурации системы CONFIG.SYS (SWITCHAR=/).
              Для версии 3.0 операционной системы  MS-DOS  ссылки  на  эту

                                      - П-48 -
         функцию и на команду SWITCHAR из CONFIG.SYS были удалены из доку-
         ментации по  операционной  системе MS-DOS. Однако,  в версии 3.30
         операционной системы MS-DOS, функция 37h прерывания 21h  выполня-
         ется также как и прежде.
              Вход: AH = 37h
                    AL = 0 (считать "символ-переключатель" (возвращаемый в
                            регистре DL)
                    AL = 1 (установить символ переключения (новый символ в
                            регистре DL)
                    AL = 2 (только версия 2.Х операционной системы MS-DOS:
                            Считать готовность устройства)
                    AL = 3 (только версия 2.Х операционной системы MS-DOS:
                            Установить готовность устройства,  где DL = 0,
                            если /DEV/ должно предшествовать  именам  уст-
                            ройств, и  DL<>0,  если  /DEV/ не должно пред-
                            шествовать именам устройств
              Возврат: DL = Cимвол-переключатель (если AL = 0 или 1
                             на входе)
                       DL = флаг готовности устройства (если AL = 2 или 3
                             на входе)
                       AL = 0FFh в случае ошибки (значение регистра AL на
                             входе не равно числу от 0 до 3)


                      Функция 50h(8): установить сегмент PSP

              Функция 50h  используется для установки сегмента для  нового
         сегмента программного префикса (PSP).
              Вход: AH = 50h
                    BX = адрес сегмента нового PSP
              Возврат: ничего
              Примечания: Для версии 2.Х операционной системы  MS-DOS  эта
                          функция не может быть вызвана внутри обработчика
                          прерывания "int 28h" без предшествующего обраще-
                          ния  к  функции 5Dh прерывания "int 21h"

                       Функция 51h(81): Считать сегмент PSP

              Функция 51h  используется для возврата адреса сегмента теку-
         щего сегмента программного префикса (PSP).
              Вход: AH = 51h
              Возврат: BX = адрес сегмента текущего PSP

                  Функция 52h(82): Считать адрес "списка списков"
                            операционной системы MS-DOS

              Функция 52h возвращает указатель на "список списков"  опера-
         ционной  системы  MS-DOS,  содержащий  различные типы информации,
         включающие указатели на другие списки информации.  В таблицах Б-2
         и Б-3 представлен формат  "списка списков"  операционной  системы
         MS-DOS.
              Вход: AH = 52h

              Возврат: ES:BX содержит адрес "списка  списков" операционной
                             системы MS-DOS.
              Примечание: Список списков, о котором идет речь, различается

                                      - П-49 -
                          для версий  2.ХХ  и  3.ХХ  операционной  системы
                          MS-DOS  (см.  таблицы Б-2 и Б-3).Несмотря на то,
                          что указанный "Список списков", вызываемый вслед
                          за функцией  52h,  содержит  информацию в первом
                          блоке диска,  это не вызывает доступа  к  диску,
                          поскольку  данная информация на диске может ока-
                          заться неточной.   Функции 1Fh или 32h,  однако,
                          дают возможность  обращаться к диску,  автомати-
                          чески обновляя данный блок диска в случае  изме-
                          нений на диске.
                                                         Таблица Б-2
             "Список списков" операционной системы MS-DOS версии 2.ХХ
         ДДДДДДДДДВДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Смещение ¦ Тип  ¦                 Данные
         ДДДДДДДДД†ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          02      ¦Слово ¦ Сегмент первого управляющего блока памяти
          00      ¦Байт  ¦ Пустой
          01      ¦Слово ¦ Указатель на первый блок диска (см.функцию 36h)
          04      ¦Байт  ¦ Не известно; указатель на первый резидентный
                  ¦      ¦ драйвер?
          08      ¦Слово ¦ Указатель  на  драйвер устройства CLOCK$
          0С      ¦Слово ¦ Указатель на фактический CON: драйвер устройства
          10      ¦Байт  ¦ Количество  логических дисководов в системе
          11      ¦Слово ¦ Максимальное количество байтов в блоке любого
                  ¦      ¦ блокового устройства
          13      ¦Байт  ¦ Неизвестно
          17      ¦Байт  ¦ Начало (не указатель) драйвера устройства NUL
         ДДДДДДДДДБДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                                                         Таблица Б-3
             "Список списков" операционной системы MS-DOS версии 3.ХХ
         ДДДДДДДДДВДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Смещение ¦ Тип  ¦                 Данные
         ДДДДДДДДД†ДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
          02      ¦Слово ¦ Сегмент первого управляющего блока памяти
          00      ¦Байт  ¦ Пустой
          01      ¦Слово ¦ Указатель на первый блок диска (см.функцию 36h)
          04      ¦Байт  ¦ Не известно: указатель на первый резидентный
                  ¦      ¦ драйвер?
          08      ¦Слово ¦ Указатель  на  драйвер устройства CLOCK$
          0С      ¦Слово ¦ Указатель на фактический CON: драйвер устройства
          10      ¦Байт  ¦ Максимальное количество байтов в блоке любого
                  ¦      ¦ блокового устройства
          12      ¦Байт  ¦ Неизвестно (возможно указатель на текущий блок
                  ¦      ¦ каталога)
          16      ¦Байт  ¦ Неизвестно (возможно массив информации на диске)
          1A      ¦Байт  ¦ Неизвестно
          20      ¦Байт  ¦ Количество блоковых устройств
          21      ¦Байт  ¦ Значение команды LASTDRIVE в файле CONFIG.SYS
                  ¦      ¦ (по умолчанию = 5)
          22      ¦Байт  ¦ Начало (не указатель) драйвера устройства NUL
         ДДДДДДДДДБДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                                      - П-50 -
             Функция 53h(83): Преобразовать блок параметров BIOS (BPB)
                                  в блок на диске

              Функция 53h  преобразует  блок параметров BIOS (BPB) данного
         диска в формат "блока на диске" операционной системы DOS и разме-
         щает информацию в указанное место (см. таблицу Б-4).
              Вход: AH = 53h
                    DS:SI = указатель на блок параметров BIOS (BPB) для
                             диска
                    ES:BP = указатель  на  область, в которой должен хра-
                             ниться блок диска операционной системы DOS
              Возврат: информация в формате блока  диска,  хранимая  в
                       области первоначально указанной с помощью ES:BP

                                                         Таблица Б-4
          Информация о структуре блока диска операционной системы MS-DOS,
                             возвращенная функцией 53h
         ДДДДДДДДДВДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         Смещение ¦ Тип    ¦           Данные
         ДДДДДДДДД†ДДДДДДДД†ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         00       ¦Слово   ¦ Количество байтов  в  секторе
         02       ¦Байт    ¦ Количество секторов в кластере
         03       ¦Слово   ¦ Зарезервированные сектора
         05       ¦Байт    ¦ Количество  таблиц FAT
         05       ¦Байт    ¦ Смещение от кластера до сектора
         06       ¦Слово   ¦ Количество элементов корневого каталога
         08       ¦Слово   ¦ Общее  количество  секторов
         0A       ¦Байт    ¦ Байт описания среды
         0B       ¦Слово   ¦ Количество секторов в таблице FAT
         ДДДДДДДДДБДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

                         Функция 55h(85): Создать блок PSP

              Функция 55h  используется для создания порожденного сегмента
         программного префикса (PSP).  Эта функция аналогична функции  26h
         прерывания 21h за исключением того, что новый PSP создается вмес-
         то копирования текущего PSP.
              Вход: AH = 55h
                    DX = адрес сегмента, в который должен  быть  уста-
                         новлен PSP
              Возврат: ничего


                  Функция 58h(88): Получить/установить стратегию
                               распределения памяти

              Вход : AH = 58h
                     AL = код функции
                     AL = 0 - получить стратегию распределения
                     AL = 1 - установить стратегию распределения
                     BL = код стратегии
                     BL = 0, если используется метод первого  подходя-
                             щего  (использует  первый  блок  в списке
                             свободной памяти, размер которого  больше
                             запрошенного или равен запрошенному

                                      - П-51 -
                     BL  = 1, если используется  метод наилучшего при-
                             ближения: (использует наименьший блок па-
                             мяти,  достаточно  большой по отношению к
                             запрошенному)
                     BL = 2, если используется метод последнего подхо-
                             дящего (использует старшие адреса послед-
                             него пригодного для  использования  блока
                             памяти)
              Возврат:  Признак  переноса  = 1 в случае ошибки, причем
                        код  ошибки  в регистре AX. Признак переноса =
                        = 0, если нет ошибки, причем код  стратегии  в
                        регистре AX.

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


             Функция 60h(96): Разложить строку пути доступа на строку
                       с полностью уточненным путем доступа

              Функция 60h принимает строку с указанием пути доступа, кото-
         рая указывает на и возвращает полностью уточненную версию того же
         пути доступа.
              Вход:  AH = 60h
                     DI:SI = указатель на строку пути доступа
                     ES:DI = указатель  на область, в которой должна
                             храниться возвращенная строка с полностью
                             уточненным  путем доступа
              Возврат:  Строка  с  полностью  уточненным путем доступа
                        возвращается в область,  первоначально  ука-
                        занную с помощью ES:DI. Не известны возвращен-
                        ные коды ошибок.


                Функция 63h(99): Получить таблицы начального байта

              Вход : AH = 63h
                     AL = подфункция
                     AL = 0 - получить системную таблицу начального байта
                          DL = 0 - очистить флаг
                          DL = 1 - установить флаг
                     AL = 2 установить временный флаг консоли
              Возврат:  DS:SI  = указатель на таблицу начального байта
                                 (если вызывается с AL = 0)
                        DL = временный флаг консоли (если вызывается
                             при AL = 2)

                                      - П-52 -

                            Приложение В.   ЛИТЕРАТУРА


                       Книги
                       Статьи



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


                                       Книги
                                       ДДДДД
                Abel, P. Programming Assembler Language.2d ed. Reston,
         VA: Reston, 1984.
                Allworth,  S.  T.  Introduction  to Real-Time Software
         Design. New-York: Springer-Verlag, 1981.
                Angermeyer, J., R. Fahringer,K. Jaeger, and D. Shafer.
         Tricks of the MS-DOS Masters. Indianapolis: Howard  W.  Sams,
         1987.
                Demarco, T. Structured Analysis and Specification. New
         York: Yourdon, 1978.
                Disk  Operating  System. Boca Raton, FL: International
         Business Mashines, 1982 (for DOS 1.10),1983 (for  DOS  2.00),
         1983  (for  DOS  2.10), 1984 ( for DOS 3.00), 1984 and 1985 (
         for DOS 3.10).
                Disk Operating System Technical Reference. Boca Raton,
         FL: International Business Mashines, 1983 (  for  DOS  2.10),
         1984 (for DOS 3.00), 1984 and 1985 ( for DOS 3.10).
                Duncan,  Ray.  Advanced MS-DOS. Redmond, WA; Microsoft
         Press, 1986.
                Hyman, Michael. Memory Resident Utilities, Interrupts,
         and Disk Management with MS  &  PC  DOS.  Portland,  OR:  MIS
         Press, 1986.
                IAPX   86/88,   186/188  User's  Manual:  Programmer's
         Reference. Santa Clara, CA: Intel, 1983.
                Kane, G., D. Hawkins, and L. Leventhal. 68000 Assembly
         Language Programming. Berkeley, Osborne/McGraw-Hill,1981.
                Kernighan,  Brian,   and   Dennis   Ritchie.   The   C
         Programming  Language.  Englewood  Cliffs, NJ: Prentice-Hall,
         1978.
                Lafore, R. Assembly Language Primer for the IBM PC and
         XT. New York and Scarborough, Ontario: New American  Library,
         1984.
               Lai,  S.  Robert.  Writing  MS-DOS  Device Drivers. New
         York: Addison-Wesley, 1987.
               Lattice 8086/8088 C Compiler Manual. New york: Lifeboat
         Associates, 1982.
               Microsoft  C  Compiler:  User's  Guide.  Bellevue,  WA:
         Microsoft, 1984 and 1985 ( for C 3.00).
               Microsoft  C: Run-Time Library Reference. Bellevue, WA:
         Microsoft, 1984 and 1985 ( for C 3.00).
               Microsoft Macro Assembler User's Manual. Bellevue,  WA:
         Microsoft,  1981  and  1983 ( for MASM 2.00), 1984 and 1985
         for MASM 4.00).

                                      - П-53 -
               Microsoft MS-DOS Programmer's Reference. Bellevue,  WA:
         Microsoft, 1981 and 1983 ( for MS-DOS 2.10 ).
               Morgan,  C.  L.  Bluebook of Assembly Language Routines
         for the IBM PC & XT. New York and Scarborough,  Ontario:  New
         American Library, 1984.
               Morgan,   C.   L.   and   M.  Waite.  8086/8088  16-Bit
         Microprocessor Primer. Peterborough, NH: BYTE/McGraw-Hill,1982.
               Norton, P. Inside the IBM PC. Bowie, MD: Robert J. Brady
         Co., 1983.
               Savitzky, Stephen. Real-Time Microprocessor Systems.  New
         York: Van Nostrand Reinhold, 1985.
               Simrin,  Steven.  The  Waite Group's MS-DOS Bible. rev.
         ed. Indianapolis: Howard W. Sams, 1988.
               Tausworthe, R.C. Standardized Development  of  Computer
         Software. Pt. I. Englewood Cliffs, NJ: Prentice-Hall, 1977.
               Turbo  Pascal  Reference  Manual  Version  2.0.  Scotts
         Valley, CA: Borland International, 1984.
               Turbo  Pascal  Reference  Manual  Version  3.0.  Scotts
         Valley, CA: Borland International, 1983, 1984, and 1985.
               Waite    Group,   The.   The   Waite   Group's   MS-DOS
         Papers.Indianapolis: Howard W. Sams, 1988.
               Yourdon,  E.  U.  and  L.  L.  Constantine.  Structured
         Design. Englewood Cliffs, NJ: Prentice-Hall, 1977.
               Yourdon,  E.  U.  Techniques  of  Program Structure and
         Design. Englewood Cliffs, NJ: Prentice- Hall, 1975.

                                      Статьи
                                     ДДДДДДД
              Duncan, Ray. "Lotus/Intel/Microsoft  Expanded  Memory", Byte
         11, no. 11, 1986 ( Special IBM Edition).
               Kaк писать программы,используя LIM EMS 3.2. Пример кус-
         ков программы  RAMDISK, использующей расширенную память.
               Hansen,  Marion,  and  John  Driscoll.  "LIM EMS 4.0: A
         Definition for Next Generation of Expanded  Memory",  MSJ  3,
         no. 1, Jan 88.
               Описание  особенностей, вводимых  LIM  EMS  4.0. Пример
         программ на "C" и на языке Ассемблера демонстрируют  улучшен-
         ные  методы  сохранения экрана, разделения данных между прог-
         раммами, а также выполнения программы в расширенной памяти.
               Lefor, John A., and Karen Lund. "Reaching into Expanded
         Memory." PCTJ 5, no.5, May 86.
               Пояснение  LIM  EMS  3.2 и AQA EEMS, ориентированных на
         приложения.Законченные образцы программ для  получения  пара-
         метров расширенной памяти и дампа данных расширенной памяти.
               Lotus, Intel,Microsoft. "Lotus/Intel/Microsoft Expanded
         Memory   Specification,   Version   4.0,  "  Document  number
         300275-005, Oct 87.
               Полная спецификация последней версии спецификации  рас-
         ширенной  памяти.  Включает  образцы  программ на языке Turbo
         Pascal и на языке ассемблера.
               Mirecki, Ted. " Expandable Memory," PCTJ, no. 2, Feb 86.
               Описание LIM EMS 3.2 и AQA EEMS. Тесты продуктов расши-
         ренной памяти фирм "Интел" и " Эй-ЭС-ТИ".
               Yao, Paul. "EMS Support Improves Microsoft Windows  2.0
         Application Performance," MSJ 3, no. 1, Jan 88.
               Обсуждение  техники использования LIM EMS 4.0 в Windows
            2.0 для управления несколькими одновременными приложениями.

                                      - П-54 -


                     Приложение Г.  СПРАВОЧНИК ПО КОДАМ ASCII
                              И ПРЕОБРАЗОВАНИЯ ЧИСЕЛ



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


              Таблица Г-1 является таблицей соответствия  значений  клавиш
         терминала в десятичном (основание 10), шестнадцатиричном (основа-
         ние 16), восьмиричном (основание 8), а также в коде ASCII (Амери-
         канский  стандартный код для обмена информацией).  Последователь-
         ности клавиш, включающие клавишу "Control" вводятся одновременным
         нажатием  клавиши  "Control" и указанной клавиши.  Эти последова-
         тельности основаны на тех  последовательностях,  которые  описаны
         для  большинства  стандартных  терминалов,  таких  как клавиатура
         "Diablo 1640" и серия терминалов "Televideo" и может быть описана
         иначе на других терминалах.


                                                         Таблица Г-1
                         Перекрестные ссылки в коде ASCII
         ДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДД
         Деся- ¦Шестнад-¦ Восьми-¦Символы ¦ Графи-  ¦  Клавиша
         тичный¦цатирич-¦ ричный ¦в коде  ¦ ческие  ¦ терминала
         код   ¦ный код ¦ код    ¦ASCII   ¦ символы ¦ фирмы "ИБМ"
         ДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДД†ДДДДДДДДД†ДДДДДДДДДДДДДДДДД
           0   ¦   00   ¦  00    ¦   NUL  ¦         ¦   <Ctrl-@>
           1   ¦   01   ¦  01    ¦   SOH  ¦        ¦   <Ctrl-A>
           2   ¦   02   ¦  02    ¦   STX  ¦        ¦   <Ctrl-B>
           3   ¦   03   ¦  03    ¦   ETX  ¦        ¦   <Ctrl-C>
           4   ¦   04   ¦  04    ¦   EOT  ¦        ¦   <Ctrl-D>
           5   ¦   05   ¦  05    ¦   ENQ  ¦        ¦   <Ctrl-E>
           6   ¦   06   ¦  06    ¦   ACK  ¦        ¦   <Ctrl-F>
           7   ¦   07   ¦  07    ¦   BEL  ¦        ¦   <Ctrl-G>
           8   ¦   08   ¦  10    ¦   BS   ¦        ¦   <Ctrl-H>
           9   ¦   09   ¦  11    ¦   HT   ¦         ¦   <Ctrl-I>
          10   ¦   0A   ¦  12    ¦   LF   ¦         ¦   <Ctrl-J>
          11   ¦   0B   ¦  13    ¦   VT   ¦        ¦   <Ctrl-K>
          12   ¦   0C   ¦  14    ¦   FF   ¦        ¦   <Ctrl-L>
          13   ¦   0D   ¦  15    ¦   CR   ¦         ¦   <Ctrl-M>
          14   ¦   OE   ¦  16    ¦   SO   ¦        ¦   <Ctrl-N>
          15   ¦   0F   ¦  17    ¦   SI   ¦        ¦   <Ctrl-O>                                      <Ctrl-F>
          16   ¦   10   ¦  20    ¦   DLE  ¦        ¦   <Ctrl-P>
          17   ¦   11   ¦  21    ¦   DC1  ¦        ¦   <Ctrl-Q>
          18   ¦   12   ¦  22    ¦   DC2  ¦        ¦   <Ctrl-R>
          19   ¦   13   ¦  23    ¦   DC3  ¦        ¦   <Ctrl-S>
          20   ¦   14   ¦  24    ¦   DC4  ¦        ¦   <Ctrl-T>
          21   ¦   15   ¦  25    ¦   NAK  ¦        ¦   <Ctrl-U>
          22   ¦   16   ¦  26    ¦   SYN  ¦        ¦   <Ctrl-V>
          23   ¦   17   ¦  27    ¦   ETB  ¦        ¦   <Ctrl-W>
          24   ¦   18   ¦  30    ¦   CAN  ¦        ¦   <Ctrl-X>
          25   ¦   19   ¦  31    ¦   EM   ¦        ¦   <Ctrl-Y>

                                      - П-55 -
         ДДДДДДВДДДДДДДДДВДДДДДДДВДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДД
         Деся- ¦Шестнад- ¦Восьми-¦Символы ¦ Графи-  ¦  Клавиша
         тичный¦цатирич- ¦ричный ¦в коде  ¦ ческие  ¦ терминала
         код   ¦ный код  ¦код    ¦ASCII   ¦ символы ¦ фирмы "ИБМ"
         ДДДДДД†ДДДДДДДДД†ДДДДДДД†ДДДДДДДД†ДДДДДДДДД†ДДДДДДДДДДДДДДДДД
          26   ¦   1A    ¦ 32    ¦   SUB  ¦     