ЧАСТЬ 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, НЕОГРAHИЧЕННОЕ ПОВТОРЕНИЕ
Повторяет блок команд
между IRP и ENDM для
каждого значения из para-
meter_list,заменяя фор-
мальный параметр (dummy)на
значение параметра из каж-
дого расширения.
IRPC dummy, строка СИМВОЛ НЕОГРAHИЧЕННОГО
ПОВТОРЕНИЯ
Повторяет блок команд
между IRPC и ENDM для
каждого символа строки,
заменяя формальный пара-
метр на символ из каж-
дого расширения.
---------------------------------------------------------
Таблица 1-2. Специальные символы в макрокомандах
---------------------------------------------------------
Символ Описание применения
---------------------------------------------------------
&argument Соединяет формальные аргументы или сим-
волы с текстом. Особенно необходим для
подстановки вместо формальных аргументов
в строках, заключенных в кавычки.
;;comment text Обозначает комментарий. Такой
комментарий никогда не приводится в
макроописании.
!char Указывает, что следующий далее символ
является литералом. Используется для
включения &, % и т.д. в макрорасширения,
когда эти символы могут быть интерпре-
тированы не как специальные.
%symbol Используется для преобразования символа
или выражения в число текущей системы
счисления.
Угловые скобки (< и >) используются для
определения текста, заключенного между
ними, как литерала. Все, что заключено
в такие скобки, может быть передано в
макро как одиночный аргумент.
----------------------------------------------------------
- 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 , IF строка1 IDENTICAL TO
строка2 (если строка1 иден-
тична строке2)
IFDIF , 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,
IFIDN <&®>,<&arg>
?isr16 = TRUE
EXITM
- 1-25 -
ENDIF
ENDM ;; конец секции IRP
;; Если сравнились, остановиться здесь
IF (?isr16)
EXITM
ENDIF
;; Если еще не сравнились, продолжить далее
IRP reg,
IFIDN <&®>,<&arg>
?isr8 = TRUE
EXITM
ENDIF
ENDM ;; конец секции IRP
;; Если сравнились, остановиться здесь
IF (?isr8)
EXITM
ENDIF
;; Если не сравнились, попробовать прописные имена регистров
IRP reg,
IFIDN <&®>,<&arg>
?sir16 = TRUE
EXITM
ENDIF
ENDM ;; конец секции IRP
;; Если сравнились, остановиться здесь
IF (?isr16)
EXITM
ENDIF
;; Если еще не сравнились, попробовать еще
IRP reg,
IFIDN <&®>,<&arg>
?isr8 = TRUE
EXITM
ENDIF
ENDM ;; конец секции IRP
ENDM ;; конец макроописания
-----------------------------------------------------------------
Cердцевиной этого макро, как и любого макро сопоставления, яв-
ляются команды:
IRP reg,
IFIDN <&®>,<&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 = to (выполнить
для каждого целого значения var между
begin и end включительно, увеличивая
или уменьшая var при каждом прохождении
цикла)
END_FOR
CASE_OF_ CASE OF
(выполнить, если var = A)
(выполнить, если var = B)
. . .
(выполнить, если 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 ,<+>
inc &p1 ;увеличить счетчик
ELSE
IFIDN ,<->
dec &p1 ;уменьшить счетчик
ELSE
; ОШИБКА - неверная спецификация шага в операторе "@For"
- 1-38 -
EXITM
ENDIF
ENDIF
first: ;проверить на необходимость продолжения
;; Вставить в программный код проверку условия
cmp &p1,&p3 ;достигнут ли конец?
;; Перейти к секции "FOR_TRUE"
IFIDN ,<+>
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'>,
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,
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,
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, ассемблировать
;;следующую строку
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,
- 1-52 -
;;ассемблировать след-ую строку
mov ah,06h ;;функция DOS по записи в
;;стандартный файл
ELSE ;;в противном случае
IFIDN , ;;если 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 не
;;пусто, ассемблировать
;;следующее
@ChkChr ;;выявить, ожидается ли символ
cmp al,0 ;; al = 0 => символ не ожидается
je bye ;;если символа нет, продолжить
ENDIF ;;конец проверки условия
IFIDN , ;;если EKOFLAG=EKO, ас-
;;семблировать
MOV AH,06H ;;функция DOS по записи
;;в стандартный вывод
ELSE ;;в противном случае
IFIDN <ЕKOFLAG>, ;;если 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
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,
- 1-54 -
;;ассемблировать следующую строку
mov ah,06h ;;функция DOS по записи в
;;стандартный вывод
ELSE ;;в противном случае
IFIDN , ;;если 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, ;;неопределен. повторение
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,
@FcnCall Fcn2,
Для любого вызова подпрограммы мы можем иметь фактически неог-
раниченное число параметров.
В данном макро имеется много недостатков. Одним из этих недос-
татков является то, что мы не покрыли все возможные значения
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
Sublibs Library
MultLibs Library
.
.
.
- 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 ; пересылка последнего аргумента
... ...
push ; пересылка второго аргумента
push ; пересылка первого аргумента
call ; вызов процедуры
add sp,<2N> ; очистка стека
... ...
; Вызываемая процедура
PROC NEAR ; пример вызова процедуры near
push bp ; сохранение старого BP
mov bp,sp ; указатель ссылки на стек
... ...
mov ,[bp+4] ; доступ к первому параметру
mov ,[bp+6] ; доступ ко второму параметру
... ...
mov ,[bp+2+2N] ; доступ к последнему параметру
... ...
mov sp,bp ; восстановление SP
pop bp ; удаление сохраненного BP
ret ; возврат в вызывающую программу
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 имя,
@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 имя,
@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
@Accept MACRO reg_list
push bp ; сохранение указателя базы
mov bp,sp ; уст-ка BP для доступа к парам.
mov &pnum,[bp+4] ; получение количества парам-ов
?count = 0
IRP reg,<®_list>
?count = ?count+1
push &® ; сохр-е рег-ра для нового знач.
mov &®,[bp+4+?count*2] ; получение параметра
ENDM
ENDM
;; **** Макрос @CRet: @CRet
@CRet MACRO reg_list ; список регистров
IRP reg,<®_list>
pop &® ; восст-е сохраненного регистра
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 ; пересылка 1-го аргумента
push ; пересылка 2-го аргумента
... ...
push ; пересылка последнего аргумента
call ; вызов процедуры
... ...
; Вызываемая процедура
StackFrame STRUC ; описание шаблона стека
dw ? ; сохраненный BP
dd ? ; адрес возврата (используйте "dw"
; для NEAR (близкий))
paramN dw ? ; последний параметр
... ...
param2 dw ? ; 2-й параметр
param1 dw ? ; 1-й параметр
StackFrame ENDS ; конец описания шаблона
;
base EQU [bp] ; база шаблона
;
PROC FAR ; пример вызова far (далеко)
push bp ; сохранение старого BP
mov bp,sp ; указатель ссылки в стеке
... ...
mov ,base.param1 ; доступ к 1-му параметру
mov ,base.param2 ; доступ ко 2-му параметру
... ...
mov ,base.paramN ; доступ к последнему пар-ру
... ...
mov sp,bp ; восстановление SP
pop bp ; сброс сохраненного BP
ret (2N) ; возврат в вызывающую программу
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
(переслать в стек непосредственные данные), однако пользователи
других микропроцессоров должны передавать данные в стек через не-
посредственный регистр. Для этого может быть использован макрос
@PushIm, рассмотренный в главе 1, однако, для этого приложения
его сложность не указывается. Если используется вызывающая прог-
рамма, приведенная в листинге 2-1, то для передачи непосредствен-
ных данных в стек доступен регистр BP (указатель базы). Почти во
всех соглашениях по архитектуре микропроцессоров 8086 для этих
целей предназначен регистр AX. Любые непосредственные данные, ко-
торые необходимо переслать в стек, передаются с помощью следующих
двух строк программы:
mov ax,
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 ; передача 3-го аргумента
push ; передача 2-го аргумента
push [старое 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 , level
;;
;; 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>,
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>,
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 (нет) или , то 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>,
@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 ;;; доступ к 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
- 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 ; макрокоманда сохранения
; регистров
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 ; восстановление регистров
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 ; сохранение некоторых регистров
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 ; восстановление регистров
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 ;;; сохранение, т.к. мы можем по-
;;; лучить при 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
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 ; сохранение всех изменяе-
; мых регистров
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
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 ; сохранение изменяемых регистров
mov ah,62h ; запрос DOS о текущем PSP
int 21h
mov DOSPSP,bx ; сохранение текущего PSP
mov ah,50h ; сообщение DOS об использовании но-
; вого PSP
mov bx,BKGPSP
int 21h ; недокументирована
popr
ret
BKGSetPSP ENDP
BKGSetDTA PROC NEAR
pushr
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
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
les di,InDosFlag ; es:di <== флаг входа в DOS
inc BYTE PTR es:[di] ; установка флага входа в
; DOS
popr
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. Хотя различие между указателем и указателем на