/
Author: Пустоваров В.И.
Tags: компьютерные технологии радиоэлектроника информационные системы язык программирования ассемблер
ISBN: 5-88547-052-9
Year: 1998
Text
В.И. Пустоваров
Язык ассемблера в программировании
информационных и управляющих
систем
Киев "ВЕК"
Москва "ЭНТРОП"
Москва "ДЕСС"
1998
ББК 32973-01
П89
УДК 681.322
Пустоваров В.И.
П89 Язык Ассемблера в программировании информационных и
управляющих систем - М.: «ЭНТРОП», К, "ВЕК", 1997.- 304 с, ил.
ISBN 5-S8547-052-9
Рецензенты: директор института информационных и моделирующих
технологий (ИМТ), доктор технических наук, профессор В.В.
Кузьмук;
заведующий кафедрой специализированных компьютерных
систем Национального технического университета Украины
«КПИ», доктор технических наук, профессор В.П. Тарасенко
На базе использования языка Ассемблера, рассмотрены основные
методы построения системных программ для широкого круга информационных и
управляющих систем, включая языковые и операционные системы.
Для специалистов и студентов в области программирования и
разработки компьютерных управляющих и информационных систем, работающих и
повышающих свою квалификацию в области повышения эффективности
программ и системного программирования
ББК 32.973-4)1
ISBN 5-88547-052-9 (русск.) © Пустоваров В.И., 1996
ISBN 5-7707-7909-8 (укр.) © Оформление ТОО "ВЕК", 1997
ОГЛАВЛЕНИЕ
ВВЕДЕНИЕ 7
ГЛАВА 1. Проблематика системного программирования и подбор средств
для решения задач 13
1.1. Классификация программ информационных и управляющих систем 13
1.2. Функции управляющих программ информационных систем 15
1.3. Требования к программам информационных и управляющих систем 16
1.4. Формализация структурного синтеза программ 17
1.5. Технология разработки и эксплуатации программного обеспечения 21
ГЛАВА 2. Архитектура и система команд процессоров семейства iX86 25
2.1. Программно-доступные объекты процессоров 25
2.2. Организация сегментированной памяти 29
2.3. Синтаксические определения записи машинных команд 32
2.4. Внутренние структуры данных процессоров 35
2.5. Основные группы машинных команд и режимы их выполнения 37
2.5.1. Операции информационных обменов 38
2.5.2. Операции информационных преобразований 40
2.5.3. Базовые операции управления 43
2.5.4. Групповые операции 47
2.5.5. Средства управления и организации вычислений 48
ГЛАВА 3. Техника модульного программирования на языке Ассемблера 50
3.1. Базовые директивы для оформления модулей на языке Ассемблера 50
3.2. Блоковая структура программы и ее данных 54
3.3. Модульное программирование с использованием языка Ассемблера 59
3.4. Базовые директивы дпя оформления модулей в языке Ассемблере 62
3.5. Общие принципы организации межмодульных связей в языках
высокого уровня 65
3.5.1. Типы данных языков высокого уровня и особенности их
реализации 66
3.5.2. Особенности организации связей в языке С 68
3.5.3. Особенности организации связей в языке Pascal 69
3.5.4. Присоединение среды языка С 75
3.5.5. Специальные модули для эксплуатации с языками высокого уровня ...75
3 5.6. Работа с ассемблерными вставками и вызовами функций MS DOS 76
ГЛАВА 4. Методика эффективного программирования на языке Ассемблера ..78
4.1. Общая методика декомпозиции задач при составлении программ 78
4 Оглавление
4 2 Общая методика повышения эффективности программ с помощью
средств Ассемблера 81
4 3. Повышение эффективности вычислительных программ 84
4.4. Программирование ветвлений и логического вывода 88
4.5. Типы циклов и их программирование 90
4.5.1. Программирование циклов с помощью счетчиков и команд
условных переходов 90
4.5 2. Программирование циклов с помощью анализа специальных
переменных 91
4.5.3. Программирование циклов с логическими условиями 92
4.6. Программирование обработки комбинированных и
структурированных данных 92
ГЛАВА 5. Особенности синтаксиса основных версий Ассемблера 98
5 1. Макросредства в Ассемблере MASM 98
5.2. Управление трансляцией в Ассемблере MASM , 102
5.3. Сложные выражения в Ассемблере и их использование 104
5 4. Управление режимами трансляции в Ассемблере MASM 106
5 5. Особенности управления трансляцией в Ассемблере TASM 108
ГЛАВА 6. Использование системы прерываний и программирование
ввода-вывода 113
6.1 Понятие прерываний и реализация в современных процессорах 113
6 2 Программные прерывания и их использование в информационных и
управляющих системах 116
6.3 Программирование ввода-вывода на физическом уровне 123
6 4. Аппаратные прерывания и их применение для организации
информационного обмена 125
6 5. Синхронизирующие примитивы, их реализация и использование 127
6 6. Проектирование программных прерываний и резидентных программ
для MS DOS 136
ГЛАВА 7 Программирование с использованием математического
сопроцессора 143
7 1. Архитектура и типы данных математического сопроцессора 143
7.2. Базовые команды математического сопроцессора 146
7.3. Административные команды' 149
7.4. Циклы и ветвления при работе с сопроцессором 151
7 5 Программирование сопроцессора с использованием операций
вычисления частичных математических функций 154
Язык Ассемблера в программировании информационных и управляющих систем 5
ГЛАВА 8. Работа с внутренними структурами данных информационных
систем 161
8.1. Структуры информационных таблиц и основные функции для работы с
ними , 161
8.2. Базовые методы и алгоритмы поиска, упорядочения и сортировки в
информационных таблицах 167
8.2.1. Работа с упорядоченными таблицами 167
8.2.2. Поиск по прямому адресуй хеш-поиск 172
8.2.3. Ссылочные и древовидные структуры 179
8.3. Методы сравнения и сопоставления аргументов и ключей поиска 183
8.4. Поиск в линейных однородных данных 184
ГЛАВА 9. Обработка управляющих данных информационных систем 189
9.1. Типы управления в виртуальных машинах информационных и
управляющих систем 190
9.2. Архитектура и система операций виртуальных машин в инженерии
знаний 194
9.3. Архитектура языковых виртуальных машин и реализация ее элементов . 196
9.3.1. Программирование лексического анализа 197
9.3.2. Программирование восходящего разбора при синтаксическом
анализе 204
9.3.3. Программирование низходящего разбора при синтаксическом
анализе 209
9.4. Построение интерпретаторов виртуальных машин 216
9.4.1. Интерпретация машинных команд 217
9.4.2. Интерпретация операторов и функций языков программирования .... 218
9.4.3. Виртуальные машины синтаксического анализа и логического
вывода 223
9.4.4. Виртуальные машины генерации объектных кодов и оптимизации
программ 225
9.4.5. Виртуальные машины информационного поиска и распознавания
образов 232
ГЛАВА 10. Управление решением задач и организация вычислительных
процессов 233
10.1. Организация многозадачности в системах персонального и
коллективного пользования 233
10.2. Особенности реализации управления задачами в системах реального
времени 241
10.2.1. Требования к ОС реального времени 242
10.2.2. Обощенная структура специализированной ОС реального времени.244
10.2.3. Выбор приоритетов вычислительных процессов и их взаимосвязь с
системой прерываний 246
10.3. Организация защищенности программ и данных 250
6 Оглавление
10.4. Структуры данных защищенного режима 254
10.5. Управление переключением задач в защищенном режиме 257
10.6. Управление информационным обменом в защищенном режиме 264
ГЛАВА i 1. Эффективное динамическое управление решением прикладных
задач 266
11.1. Особенности использования объектно-ориентированного подхода для
решения задач управления 266
11.2. Динамическая компоновка 270
11.3. Эффективная компоновка и выполнение модулей 275
ГЛАВА 12. Отладка и тестирование программ на уровне машинных
команд 285
12.1. Задача проверки правильности программ и ее типовые решения 285
12.2. Составление контрольных примеров для проверки правильности
программ 289
12.3. Программные отладчики и методика их эксплуатации 290
12.4. Типичные ошибки и коррекция программ 292
12.5. Основы аппаратно-программной отладки 296
ЛИТЕРАТУРА 298
ВВЕДЕНИЕ
Информационные и управляющие системы в настоящее время
составляют основную область приложений компьютерных систем во всех видах
хозяйственной, финансовой, творческой деятельности, а также в образовании,
фундаментальных и прикладных научных исследованиях. Хотя современные
компьютерные технологии больше обслуживают потребности человека и групп
людей в их общественной и технической деятельности, чем их естественные
потребности, они уже становятся неотъемлемой частью повседневной жизни.
Принцип работы информационных и управляющих систем основан на
создании встроенных моделей предметной области, общих моделей
контролируемых объектов пользователя в этой области и их частных экземпляров,
отражающих конкретные свойства объектов. В основу современных методов
решения задач положены тематические и информационные модели
естественных и искусственных объектов, используемых человеком в повседневной жизни
и общественной деятельности. Прагматика создания и применения моделей
компьютерных систем основана на стремлении человека к созданию более
комфортных жизненных условий и условий для общественной и экономической
деятельности.
Накопление моделей объектов и ситуаций в виде некоторой
информационной базы позволяет более глубоко оценить ситуацию и принять решение о
требуемых воздействиях на объекты и ситуацию, т.е. об управляющих
воздействиях на окружение человека или объекты его хозяйственной деятельности.
Эта связь между информационной и управляющей частью компьютерных
систем органична и неразрывна. Различия между ними в основном определяются
уровнем автоматизации накопления информации и принятия решений в
конкретных системах.
Особенности науки об управлении, называемой кибернетикой, и науки о
способах представления и структурирования данных моделей, называемой
информатикой, состоят в том, что первая из них использует математические модели и
методы, основанные на численном представлении характеристик объектов, а
вторая акцентирует внимание на способах представления данных и создания
эффективных информационных и управляющих структур для хранения
характеристик объектов и их взаимосвязи. Принципиальное отличие их методов состоит в
том, что методы и средства кибернетики основаны на математическом анализе
непрерывных характеристик объектов, а методы и средства информатики - на
методах дискретной математики и логики. Однако параллельное развитие этих наук
привело к серьезному взаимному влиянию и взаимному проникновению их методов.
Базовым техническим инструментом кибернетики и информатики уже в
течение десятилетий остаются средства вычислительной или компьютерной
техники. Методы и технологические системы ее применения строятся на
принципах кибернетики и информатики и составляют основу математического и
программного обеспечения. Пользователь получает готовую к применению
компьютерную систему в виде аппаратуры, часто называемой английским
словом hardware, и программного обеспечения (ПО), называемого software, в ко-
8
Введение
тором отражены модели и методы математического обеспечения. Возможность
одновременного решения многих задач на компьютерах определяется
необходимыми информационными скоростными характеристиками вычислительных
средств, поэтому расширение сферы применения компьютерной техники в по-'
следнее время обусловлено в первую очередь ростом производительности и
информационной емкости вычислительных систем.
Успехи в развитии аппаратуры в настоящее время определяются
повышением степени интеграции элементной базы, развитием технологий
параллельной обработки информации и коллективном использовании сетевых
распределенных информационных систем со всеми их внутренними ресурсами
и возможностями.
Успехи в развитии программного обеспечения связываются в первую
очередь с расширением функций компьютерной техники, которые в свою
очередь требуют ускоренного роста необходимой информационной емкости и
производительности вычислительных систем. Успехи в совершенствовании
специализированных технологий и методов обработки скрыты от пользователя
внутри ПО, однако на фоне быстро возрастающего уровня автоматизации
построения информационных систем их скоростные характеристики улучшаются
сравнительно медленно, что приводит к внутреннему накоплению проблем в
программировании. Например, на сегодняшний день остается нерешенной
задача разработки эффективных средств реализации систем искусственного
интеллекта (СИИ).
Проектирование системного и специального программного обеспечения
информационных и управляющих систем разных типов требует использования
полного спектра средств автоматизации программирования для получения
необходимой скорости выполнения критических участков программ. Если
вопросы применения машинно-ориентированных языков в построении
простейших системных программ, включаемых в состав операционных систем,
освещены в обширной литературе достаточно хорошо, то их возможности для
решения специальных задач информационных и управляющих систем сохраняются
фирмами-разработчиками ПО и аппаратно-программных технологий как
элемент know-how.
Эта книга ставит своей задачей продемонстрировать возможности
рационального применения языка Ассемблера для разработки эффективного
системного и специального ПО информационных и управляющих систем. В
системное ПО традиционно включают системные управляющие и системные
обрабатывающие программы, выполняющие внутренние функции управления
и автоматизации обслуживания вычислительных систем и комплексов, которые
следует отличать от прагматических или целевых функции промышленных
информационных и управляющих систем. Системные управляющие программы в
настоящее время поставляются фирмами-разработчиками и
фирмами-дистрибьютерами в виде инсталляционных пакетов операционных систем (ОС) и
драйверов специфических и специальных устройств. Системные
обрабатывающие программы поставляются в виде дистрибутивных пакетов, включающих
компилирующие и интерпретирующие программы в качестве аппарата, реали-
Язык Ассемблера в программировании информационных и управляющих систем У
зующего языковые средства и библиотечные программы, позволяющие
эксплуатировать ПО в составе вновь разрабатываемых программ.
Специальное программное обеспечение информационных и
управляющих систем часто оказывается скрытым в составе драйверов оборудования или
поставляется в виде библиотек функционального расширения языков
программирования. К специальным программам систем таких типов относятся
программы управления базами данных и языком интерфейса информационных
систем. В информационно-измерительных системах и комплексах, включая
бортовые, с требованием повышенной надежности функционирования
вычислительного оборудования и управляющей системы в целом к этому классу
относят программы сбора и предварительной обработки информации.
Книга охватывает базовые вопросы программирования с
использованием языка Ассемблера для семейства процессоров ix86, используемых в
информационных системах, выполняющих преобразование и перекомпоновку
информации и ориентированных на решение задач информационного поиска,
анализа и автоматизированного обобщения. Кроме того рассмотрен широкий
круг задач управления вычислительными процессами, подсистемами сбора
информации и внешними автоматизированными системами к которым
предъявляются предельные требования по скорости вычислений. Основной упор сделан
на общую методику программирования на машинно-ориентированных языках,
что позволяет применять знания для проектирования программ с
использованием языка Ассемблера любого целевого процессора.
Ассемблер семейства процессоров ix86 фирмы Intel не следует
рассматривать как уникальный или главный Ассемблер микропроцессорных
систем. В настоящее время получили достаточно широкое распространение
процессоры десятка других западных фирм, прежде всего, Digital Equipment
Corporation (DEC) и Hewlett-Packard (HP), которые имеют системы команд и
языки Ассемблера существенно отличающиеся от ix86, но при их
программировании используются те же принципы. Таким образом, для программирования
на языках уровня Ассемблера используется общая методика и частные
технические приемы, связанные с особенностями архитектуры и системы команд
целевого процессора.
В структуру книги вошли следующие материалы. Вводное описание
системы команд персональных компьютеров и сетевых серверов, построенных на
процессорах типа ix86 наиболее распространенных в странах СНГ благодаря
относительной дешевизне. Описание типовых ресурсов ОС, а также сред и
окружений и методов эффективного взаимодействия с ними пользовательских
и прикладных программ, кратко называемых в переводной литературе
приложениями. Типовые подзадачи управляющих, операционных и
информационных систем с указанием эффективных методов решения и приемов,
обеспечивающих эффективное использование ресурсов вычислительной системы.
Программы и их фрагменты, приведенные в книге, оформлены не как
компоненты одного или нескольких программных комплексов, а как
иллюстрации применения технических приемов и в них возможны умолчания, связанные
с конкретными условиями применения. При достаточном уровне знаний чита-
1 О Введение
телей программы легко могут быть достроены для включения в приложения и
системные программы.
Список литературы построен так, что первые 4 позиции определяют
фундаментальные источники, которые можно использовать для получения
более подробной информации по системе команд основных модификаций
базового процессора с соответствующей цифрой в названии.
Автором обобщены и систематизированы результаты многолетней
работы возглавляемой им лаборатории информационно-вычислительных
систем кафедры вычислительной техники Национального технического
университета Украины "Киевский политехнический институт" по построению
информационно-измерительных, информационно-накапливающих, транслирующих и
управляющих систем, а также подсистем инженерии знаний для ряда
наукоемких отраслей промышленности бывшего Советского Союза и Украины.
Для систематизации материалов научных разработок, включая
фрагменты промышленно-эксплуатируемых систем, использовались пять базовых
подходов: формализованный, информационный, прагматический, системный и
ресурсный. Они позволили, по мнению автора, упорядочить знания в области
системного программирования для информационных и управляющих систем с
использованием машинно-ориентированных языков программирования.
Формализованный подход ориентирует разработчика системных
программ на построение локальных аналитических моделей системных
программ и их объектов даже в том случае, когда программисту может
показаться достаточным использование словесно неформальной модели.
Преимущество формализации проявляется при отладке программ, их верификации с
помощью формальных доказательств и определении границ применимости
разрабатываемых программ. При формализованном подходе применяется
целый комплекс моделей:
• неформальное словесное описание;
• информационная модель предметной области, включающая общее и
конкретизированное определение структур данных;
• информационная модель, в которой определяется комплекс
характеристик модели объекта и форматы их представления;
• информационно-аналитическая модель, описывающая связи между
информационными блоками; прагматическая модель, определяющая
комплекс рациональных целей решения задач в предметной области;
• ресурсная модель, определяющая базовые методы и типовые
эффективные ресурсы для решения задач.
Неформальное описание служит мостиком для перехода от словесно
описанных объектов предметной области и целей задач к математическим
методам анализа естественных и синтеза искусственных объектов.
Информационные модели предметной области и объекта обращают
внимание ведущих разработчиков систем, называемых за рубежом системными
аналитиками, на целесообразность эффективного представления данных в
системе как по информационным характеристикам, так и по характеристикам
эффективности их обработки.
Язык Ассемблера в программировании информационных и управляющих систем I I
Прагматический подход опирается на определяющее значение цели
решения задач. Формально выраженная цель включает перечень целевых
характеристик или результатов и критериев, представляющих ряд желательных
тенденций для результатов. Цель определяет направление формальных
преобразований связей, заложенных в аналитических моделях предметной области, в
последовательность действий по решению задачи, составляющих алгоритм или
программу решения задачи.
Системный подход стимулирует соблюдение корректности систем по
разным направлениям: связей с моделью предметной области, внутрипро-
граммных связей по управлению и данным и связей с внешним оборудованием
программно-аппаратного комплекса для построения целевой управляющей и
информационной системы.
Ресурсный подход состовляет основу проектирования программ как
планирования последовательности применения ресурсов ОС, языков
программирования, пакетов прикладных и системных программ и другого ранее
разработанного прикладного ПО ОС или операционного окружения, а также
эффективного использования ресурсов процессора.
Представленная в книге методика изложения была положена в основу
материалов лекций по курсу "Системное программирование" (СП), читаемого
автором для студентов специальности "Вычислительные машины, комплексы,
системы и сети" факультета информатики и вычислительной техники всех
форм обучения Киевского политехнического института в течение более 15 лет.
В современной инженерии вычислительных комплексов (ВК) методы СП
находят широкое применение при автоматизации программирования,
проведения контроля и диагностики аппаратных средств, научных исследований, а
также в различных системах автоматизации проектирования, там где
необходимы оптимальные затраты системных ресурсов. Особенное значение этот курс
приобретает в связи с тем, что является основой для самостоятельной
разработки и использования ВК различных уровней и конфигураций, для чего
требуется изучение основных средств СП и приобретение навыков
программирования ОС и их элементов.
Другое назначение книги состоит в предоставлении широкому кругу
компьютерных пользователей информации о комплексе парадигм машин
инженерии знаний и методики программной реализации виртуальных машин для
управления сложными информационными процессами в СИИ. По существу
такой подход требует первичного построения новой архитектуры процессора,
последующей ее программной реализации через систему команд виртуальной
машины. Логическим завершением построения такой машины может быть
создание машинно-ориентированного языка для процессора с разработанной
архитектурой. Предложенные материалы позволяют облегчить восприятие
реализации таких сложных средств автоматизации программирования как язык
логического программирования Prolog и объектно-ориентированные языки
программирования.
Следуя зарубежным традициям автор считает необходимым упомянуть
специалистов, сформировавших его кругозор по предметной области этой
книги: В.И. Сапрыкина, Ф.П. Яремчука, К.Г. Самофалова, В.Я. Крупеня и сотруд-
12
Введение
ников, внесших существенный вклад в становление научной лаборатории
системного программирования В.И. Салапатова, Н.М. Бондаренко, А.С. Метлуш-
ко, Ю.В. Зайцева, В.И. Черновола, Т.Г. Дябло, А.П. Кушниренко, А.А. Дро-
бязко, Г.О. Прусса, И.В. Кудельского, А.В. Козлова, С.А. Лиличенко и А.В. Ро-
манчука.
Автор считает необходимым выразить особую признательность
специалистам по-прежнему сотрудничающим с лабораторией, чьи материалы
послужили основой систематизации методов и приемов системного
программирования: С.К. Гончаруку, Т.В. Мусиной, А.В. Кузнецову, А.И. Тюрютикову, А.И.
Флерову, а также аспирантам кафедры Т. Соболь и X. Соболь (Польша) и
студенту А. Клещевникову. Большой вклад в создание и оформление рукописи
книги внесли жена автора Нина и сыновья Владимир и Анатолий, младшему из
которых автор признателен за ряд ценных замечаний и контроля
работоспособности ряда примеров.
Автор благодарен издательству "ВЕК" и особенно С.Г. Стиренко за
инициативу и большую помощь в издании этой книги.
ГЛАВА 1
ПРОБЛЕМАТИКА СИСТЕМНОГО
ПРОГРАММИРОВАНИЯ И ПОДБОР
СРЕДСТВ ДЛЯ РЕШЕНИЯ ЗАДАЧ
Эта глава определяет общую методику тхщюения щюграмм и
ос обенности ее применения при программировании на языке
Ассемблера процел(ofxxi разнообразных типов для задач
программирования ин(/юрмационныл и управляющих систем.
1.1. Классификация программ информационных и
управляющих систем
Подавляющее большинство наиболее распространенных современных
вычислительных систем (ВС) используется для хранения, накопления и
автоматизированной обработки информации для нужд автоматизированного
управления производством на различных уровнях и составления информационных баз,
предназначенных для удовлетворения хозяйственных нужд в различных
режимах от обучения персонала до автоматизированного или автоматического
ввода исходных данных, отображения результатов и выдачи управляющих
воздействий на объекты управления или автоматизированного формирования
текущих и итоговых документов практически во всех отраслях деятельности человека.
Традиционная классификация программных средств различает
программы пользователей, часто называемые прикладными или проблемными, и
системные программы, поддерживающие работу вычислительных систем,
комплексов и сетей в автоматическом режиме. Программные средства
пользователей включают в себя исполняемые программные модули или пакеты программ
и библиотеки объектных модулей - комплексы долговременно сохраняемых
программ для решения разнообразных задач из узкой предметной области
пользователя.
К классу системных программ относят специальные программы,
обеспечивающие автоматизированное выполнение и программирование задач в
вычислительных комплексах (ВК) при составлении, преобразовании в
исполнимую форму и выполнении прикладных программ. Для решения задач в ВК
создаются пользовательские вычислительные процессы (ВП) на центральном
процессоре ВК и процессы обмена с внешними устройствами, которые
выполняются по программам, входящим в состав операционной системы (ОС). Программы
ОС в сочетании с аппаратными ресурсами реализуют внутренние управляющие
функции ВК, направленные на управление информационными,
операционными, управляющими и коммуникационными ресурсами.
1 4 Глава 1 Проблематика системного программирования и подбор средств для решения задач
При таком подходе к классу информационных и управляющих
компьютерных систем можно отнести большинство типов аппаратно-программных
комплексов, исключая лишь некоторые виды математических вычислений, не
влияющие на процессы управления. При этом нужно учитывать, что при
развитии компьютерных систем часто употребляемые функции типовых проблемных
программ поднимают для использования в различных приложениях сначала на
уровень системных программ, а затем наиболее распространенные и
критические по временным затратам на уровень частичной или полной аппаратной
реализации. В последнее десятилетие подобный путь от программной
реализации и до частично аппаратной прошли средства управления
многопрограммным защищенным режимом в процессорах фирмы Intel, а путь от прикладных
программ до системных управляющих - средства управления диалоговым
взаимодействием с пользователем в системных оболочках (типа Windows)
различных фирм для разнообразнейших сетей и терминальных станций.
Управляющие системные программы, организующие корректное
выполнение процессов и функционирование всех устройств системы при решении
задач, как правило, постоянно сохраняются в основной памяти. Они составляют
так называемое ядро ОС и являются резидентными программами (resident), а те
управляющие программы, для которых не хватает места в основной памяти, и
которые загружаются в память непосредственно перед выполнением, принято
называть траюитными (transitive) программами. Обрабатывающие системные
программы выполняются как специальные проблемные задачи или приложения ОС
(applications), решаемые пользователем или программистом при создании
новых и модификации ранее разработанных программ.
Требования к системным программам формулируются из соображений
прозрачности или незаметности работы служебных программ, а также
соблюдения условий гарантированной надежности выполнения в соответствии с
функциональными требованиями, называемыми спецификациями, максимальной
скорости выполнения, минимальных затрат на сохранение машинных кодов и
использование стандартных средств связи с проблемными программами. Минимизация и
упрощение действий пользователей при эксплуатации системных программ привели
к тому, что уже на протяжении последних десятков лет системные и проблемные
программы поставляются в виде установочных или инсталляционных пакетов,
позволяющих каждому пользователю выбрать наиболее приемлемый режим
эксплуатации программ в соответствии с имеющимися условиями и задачами.
Требования высокой эффективности системных программ вызывают
необходимость использования специальных языков СП, к которым принадлежат
машинно-ориентированные языки типа языка Ассемблера, и языки высокого
уровня с развитыми формами представления внутренней, прежде всего,
адресной информации системных программ, например, С или PL/M. К типам данных
этих языков отнесены указатели на данные разных типов или адреса данных и
программных объектов. Кроме того, большинство современных систем
программирования, ориентированных на численное решение математических задач,
в том числе практически все Pascal-системы, имеют возможность включения
вставок на языке Ассемблера. Корректность вставок определяется знанием
внутренних правил использования ресурсов процессора в языковой системе.
Язык Ассемблера в программировании информационных и управляющих систем I 5
Эффективность выходного кода компиляторов с языков
программирования высокого уровня определяется качеством генераторов машинного кода,
которое далеко не всегда приближается к качеству программирования на языке
Ассемблера высококвалифицированным специалистом. Причины недостаточно
эффективного машинного кода, генерируемого компилятором, вытекают из
сложности:
• расширения выходной лексики компиляторов до уровня машинных
команд целевого процессора и эффективных типовых комбинаций машинных
команд;
• комбинирования разных моделей представления абсолютных и
относительных адресных данных в сложных информационных структурах;
• организации, систематизации и эффективного кодирования
нестандартных и перечислимых типов данных (т.е. типов с перенумерованными
значениями);
• эффективного и наиболее полного использования ресурсов в областях
предпочтительного применения;
• построения гибких и достаточно обширных моделей эквивалентных
преобразований.
В этих условиях языки Ассемблера, которым оптимисты в области
программирования уже несколько десятилетий предрекают скорую гибель, сохраняют
определенное значение в областях, где требуется предельная эффективность
машинных кодов. Более того, можно сравнительно спокойно утверждать, что
машинно-ориентированные языки или, по крайней мере, машинно-ориентированные
понятия сохранятся в базовых системах разработки программного
обеспечения. Например, на уровне транслирующих программ до тех пор, пока
продолжается исследовательский процесс совершенствования архитектур процессоров
и структур компьютерных систем. Появление новых разнообразных архитектур
процессоров, ориентированных на параллельные вычисления и на эффективное
решение новых классов задач преобразований, отнюдь не предвещает скорого
окончания процесса развития процессорных архитектур, а следовательно и
машинно-ориентированных языков.
1.2. Функции управляющих программ
информационных систем
Программные комплексы или продукты выполняют системные функции
и целевые функции прикладной задачи. Целевые функции информационных
систем, как правило, обеспечивают создание комфортных условий для
различных видов человеческой деятельности за счет автоматизации накопления и
контроля информации об объектах поддержания жизнеобеспечения и
хозяйственной деятельности человека, а также в организации управления экспериментами и
научными исследованиями.
В настоящее время хозяйственная деятельность человека опирается на
численные модели и текстовые описания предметной области, включающие:
I 6 Глава 1 Проблематика системного программирования и подбор средств для решения задач
• познавательные модели для представления фундаментальных связей в
экономических, естественных и технических предметных областях;
• нормативные модели, которые обеспечивают рациональное управление
хозяйственной, технической и экономической деятельности;
• модели проектирования и планирования во всех видах деятельности;
• модели, основанные на фактах и отображающие экономическое и техническое
состояние производства на конкретном хозяйственном и техническом объекте.
Современные модели подобных типов в основном строятся на основе
аналитико-алгебраических и реляционных моделей, встроенных в
управляющие программы. Однако совершенствование средств анализа
естественно-языковой информации приводит ко все более широкому распространению
естественно-языковых моделей и использующих их систем управления.
Основные системные функции управляющих программ составляют
управление ВП в ВК, а также внутренними структурами данных информационных
систем. Современный уровень компиляторов позволяет эффективно решать
задачи управления с точностью до выходной лексики компилятора. Однако
уникальные операции управления, реализованные аппаратно на уровне машинных
команд, практически не находят отображения в выходных кодах
компиляторов. Поэтому системные программы, ориентированные на управление
вычислительными процессами, по-прежнему удобно разрабатывать,
анализировать и эксплуатировать с использованием средств языка Ассемблера.
Почему и в каких случаях целесообразно использовать язык
Ассемблера? Прежде всего, это обусловлено сложностью построения эффективных
машинно-ориентированных генераторов объектных кодов в случае
специализации использования ресурсов процессора, а также сложность генерации
специфических эффективных комплексов команд. Условия предельных требований к
скорости обработки в программах управления и требование приемлемого
времени решения в больших информационных системах стимулируют поиск
новых, более эффективных методов решения и связанные с этим работы по
усовершенствованию процессорных структур и архитектур. Эти работы в большей
степени ориентированы на общее повышение уровня параллелизма в ВК и в
меньшей - на специализированные устройства повышенной эффективности.
В этой книге, в качестве базового, выбран язык Ассемблера
персональных компьютеров типа IBM/PC, использующих процессоры семейства ix86/87.
Общие принципы программирования на языке Ассемблера не зависят от
архитектуры процессора, но конкретные программы строятся в четком
соответствии с архитектурой и системой команд.
1.3. Требования к программам информационных
и управляющих систем
Требования к программам информационных и управляющих систем
можно сформулировать с позиций функционального и организационного
определения целей. Базовые функции таких систем - это:
Язык Ассемблера в программировании информационных и управляющих систем I #
• поиск информации по разным типам поисковых образов;
• выполнение эквивалентных и адекватных преобразований для
приведения образов к сопоставимой форме;
• анализ и распознавание образов по входным данным;
• анализ ситуаций в системе в ретроспективном и перспективном плане;
• формирование планов и проектов действий в рамках информационной
или управляющей системы.
Организационные цели связаны с необходимостью эффективного
использования оборудования, имеющегося в наличии, и приемлемого времени
решения задач, что, как следствие, приводит к требованию максимальной
скорости выполнения типовых операций:
• быстрое выполнение сопоставлений оперативных данных с эталонами;
• быстрое и целенаправленное продвижение по таблицам эталонов при
поиске решений;
• использование прагматически эффективных кодов для быстрого
выполнения операций поиска;
• простота и гибкость перестройки для новых условий и аппаратуры
применения;
• использование групповых и кооперируемых операций процессов.
В состав пакетов системных программ наряду с основными
программами, имеющими возможность реконфигурации, включаются специальные
настроечные программы, называемые программами инсталляции (installation).
В большинстве информационных и управляющих систем наблюдается
существенный рост временных затрат с ростом объема хранимой и
обрабатываемой информации. Поэтому в настоящее время наряду с повышением
производительности процессоров ведется поиск новых радикальных методов
повышения производительности, которые опираются на исследования различных
информационных и функциональных моделей.
1.4. Формализация структурного синтеза программ
Основу формализации процесса составления алгоритма решения задач
создает методика сопоставления формализованных задач с возможностями
ресурсов ВК, доступных пользователю, путем поиска аналогий и декомпозиции
задач. Ресурсы, доступные программисту можно систематизировать по
уровням языковых конструкций, функциональных и процедурных расширений и
объектно-ориентированных конструкций. Функциональные, процедурные и объектно-
ориентированные ресурсы оформляются как библиотеки языков
программирования, библиотеки специализированных ОС, специализированные библиотеки
пользователей и интегрированные пакеты программ. При проектировании
программ целесообразно движение в направлении от задач и ресурсов более
высокого уровня до более низкого уровня. Поэтому один из путей
формализации построения алгоритмов и программ - определение проекций задачи на базо-
1 8 Глава 1 Проблематика системного программирования и подбор средств для решения задач
вые ресурсы текущего уровня с постепенным понижением уровня ресурсов..
Другой способ связан с эквивалентными преобразованиями аналитических связей к
виду формул прямого вычисления целевых характеристик.
Декомпозиция выполняется на базе анализа структур данных и их
аналитических связей как разбиение базовой цели, объектов задач и связанных с
ними вычислений на более простые последовательные, условные и циклические
составные части, что сводит решение задач к выполнению последовательных
функциональных преобразований, итерационных, динамических или
рекуррентных. Обычно декомпозицию задач начинают с декомпозиции данных и создают
схему программы, адекватную входным и выходным данным, а также структурам
аналитических связей и ограничений. Декомпозиция данных может быть
реализована в одной из трех форм.
1. Детализация данных, выполняемая, когда первоначально
спроектированные форматы и типы данных или объектов не реализованы в рамках
оборудования или выбранного языка программирования целевой ЭВМ.
2. Декомпозиция связей и действий, выполняемая при наличии
используемых типов и форматов данных и отсутствии необходимых действий для
обработки этих данных для получения полезных целевых результатов.
3. Декомпозиция через абстрагирование, реализуемае как выделение
общих свойств группы объектов, начинаемае с поиска аналогий и заканчиваемае
отбрасыванием несущественных свойств объекта и введением обобщающих
понятий.
4. Четвертая форма декомпозиции непосредственно не связана со
структурами данных и обеспечивается трансформацией аналитических
выражений связей в аналитические формулы вычислений, задающие
последовательность действий или операций. Такие преобразования и их результаты (в том
числе рекурсивные) рассматриваются как один из элементов обобщенной
декомпозиции последовательности решения задачи.
Спецификация задачи должна включать описание цели решения задач в
сфере исследований, включая цель проектирования объектов с предварительно
определенными свойствами, а также цель исследования поведения объектов в
координатном пространстве сферы исследований с определением их статистических
и динамических свойств.
Формализация проектирования программ перспективна, по крайней
мере, с позиций автоматизации сопровождения разрабатываемых программ и
их комплексов. С формальной точки зрения разработка программ для решения
комплекса задач практически в любой предметной области традиционно
состоит в преобразовании аналитических формальных моделей в последовательность
целенаправленных действий для получения значений целевых переменных. При этом
целенаправленные действия реализуются ресурсами выбранного языка
программирования. В случае использования языка Ассемблера в спецификацию
решения задач приходится дополнительно включать и описания используемых
элементов машинной архитектуры.
Программное управление вычислениями в наиболее распространенных
современных компьютерах рассматривается как выполнение последовательно-
Язык Ассемблера в программировании информационных и управляющих систем I Э
сти управляющих кодов машиной со структурой фон-Неймана, включающей
центральный процессор, а также главную или оперативную память, где хранятся
программы и обрабатываемые данные.
Особенности ресурсов языка Ассемблера заключаются в том, что
символические обозначения соответствуют таким машинным объектам, как
машинные команды, регистры, области памяти, сохраняющие данные и коды
программ, указатели или адреса областей памяти, в то время как в языках
программирования высокого уровня ресурсы связаны с математическими понятиями
структур данных, операций, связей и ограничений.
Целесообразность составления отдельных программных модулей и
фрагментов программ на языке Ассемблера обусловлена недостаточной
эффективностью объектных кодов, генерируемых современными компиляторами
языков высокого уровня, связанной со сложностью включения в выходную
лексику компиляторов специальных и комплексных машинных команд, а также с
наличием эффективных комплексов и групп команд. Кроме того, определенные
трудности при проектировании генераторов эффективных кодов вызывает учет
особенностей эффективного использования таких специализированных
ресурсов как регистры, предпочтительные для работы в качестве счетчиков и
аккумуляторов результатов, в отличие от индексных и базовых регистров.
Следствием указанных причин является относительно редкое использование
машинно-ориентированных языков для программирования так называемых RISC-
процессоров, т.е. процессоров с сокращенной системой команд (reduced
instruction set) и достаточно широкие возможности их применения для CISC-
процессоров со сложными командами (complicated instruction set),
реализованных аппаратно-программными средствами.
RISC- и CISC-подходы к построению процессоров являются не столько
альтернативными или взаимоисключающими, сколько ортогональными или
разнонаправленными путями развития оборудования. Они могут в
разнообразных сочетаниях использоваться на разных уровнях реализации
математического и программного обеспечения:
• аппаратная реализация микроопераций;
• микропрограммирование при реализации набора команд процессора;
• программирование эффективного решения задач;
• управление процессами эффективного решения задач.
RISC-подход вытекает из целесообразности устранения всего лишнего и
неэффективно используемого оборудования процессоров в результате
оптимизационного анализа структуры вычислительной системы. CISC-подход
является следствием развития математических методов решения задач и желанием
получить наиболее высокие скорости решения, не обращая внимания на
относительную неэффективность использования оборудования. Следовательно, они
являются диалектическими рычагами развития процессорного оборудования,
что в процессорах ix86 проявилось в применении RISC-подхода для построения
унифицированных методов адресации в защищенных режимах и CISC-подхода
для расширения возможностей адресации и ускоренного выполнения комплек-
20 Глава 1 Проблематика системного программирования и подбор средств для решения задач
сов действий, часто используемых для управления вычислительными
процессами.
Для изучения методов непосредственного программирования главного
процессора и построения машинно-ориентированных языков
программирования решающее значение имеет архитектура, определяемая типами программно-
доступных ресурсов вычислительной системы:
• информационные ресурсы для накопления, запоминания или сохранения
данных (регистры, главная, стековая и расширенная память, внешние
накопители, включая внутреннюю организацию их взаимодействия);
• операционные ресурсы главного процессора, сопроцессоров и
дополнительных процессоров, выполняющих действия по пересылке и
преобразованию информации;
• управляющие ресурсы, определяемые командами изменения
управляющей последовательности команд или программы главного процессора;
• коммуникационные ресурсы, обеспечивающие связь с внешним миром,
которые становятся заметными для программиста при проектировании
драйверов и ВП.
Архитектура главного процессора характеризуется доступными для
программиста регистрами данных, базовых адресов и индексов, или регистрами общего
назначения, идентифицируемые названиями или номерами, а также регистрами
управления и состояния, в которых хранятся указатели и флаги или признаки
результатов. Система команд процессора определяется возможностями
арифметико-логических устройств и форматами данных процессора.
Информационные ресурсы, прежде всего, включают регистры, оперативную память,
подключенную к основному адресному пространству, и средства расширения основной
памяти.
Специфика системного программирования определяется основным
назначением системных программ: способствовать решению задач и составлению
программ для вычислительных машин, комплексов, систем и сетей. Системные
программы объединяются в ОС, которые обеспечивают взаимодействие
пользователя с системами программирования, выполняющими работу в рамках
определенного языка и обслуживающими оболочками или утилитами и ВК, а
также эффективное использование ресурсов ВК.
Для разработки эффективных системных программ языки
программирования должны учитывать особенности аппаратуры ВК, оперировать такими
объектами, как биты, байты, слова, строки, сегменты и блоки памяти, содержимое
регистров, символы, целочисленные знаковые и беззнаковые данные разной длины
и т.д. Сейчас часто используется соединение языка С с Ассемблером, именно
оно использовалось для создания ОС UNIX и других системных программ.
Программы, составленные на языке Ассемблера
высококвалифицированным программистом, компактнее и выполняются быстрее программ,
составленных на других языках. Ассемблер обеспечивает потенциально
максимальную продуктивность, гибкие возможности контроля над ЭВМ при условии
рационального и полного использования возможностей запоминающих
(регистры и память) и операционных (множество команд) ресурсов ВК, анализ ко-
Язык Ассемблера в программировании информационных и управляющих систем 21
торых требует больших трудозатрат при распределении ресурсов
программистом.
С учетом незначительных принципиальных отличий языка С от языка
программирования Pascal, принятого за базовый во многих учебных
заведениях, и необходимостью более детального изучения принципов программного
управления на уровне машинных команд базовым языком книги выбран язык
Ассемблера персональных ЭВМ типа IBM PC. Этот язык в основном
определяется комплексом командно доступных аппаратных средств процессоров, т.е.
архитектурой, и системой команд процессоров, поэтому для принципиально
разных процессоров, имеющих разные системы команд, синтаксис языков
Ассемблера практически несовместим. Существенно влияет на язык Ассемблера и
наличие в структуре ВК математических процессоров и специальных процессоров
информационного обмена. В Украине и странах СНГ сейчас наиболее
распространены ПЭВМ на основе семейства микропроцессоров фирмы Intel с
базовыми процессорами 8086 и 8088, архитектура которых допускает подключение
математических сопроцессоров 8087, и усовершенствованных процессоров il86,
i286, i386, i486 и Pentium, программно совместимых с предыдущими
модификациями.
Ядро ОС составляют программы, обеспечивающие управление
процессом выполнения операций, диспетчеризацию очередей, планирование и
обработку прерываний, организацию разделов памяти, сегментацию, динамическое
и оверлейное размещение модулей, управление внешними устройствами,
обработку запросов ввода-вывода, выделение памяти, буферизацию данных;
управление и синхронизацию ВП; управление данными, ведение каталогов.
Окружение ядра ОС составляют утилиты, редакторы, компиляторы и другие
обслуживающие программные средства. Ядро ОС постоянно находится в главной
памяти, а остальное ПО сохраняется на внешних носителях.
7.5. Технология разработки и эксплуатации
программного обеспечения
Для построения системных и проблемных программ будем использовать
принципы первичного построения объектно-ориентированной
информационной модели сферы исследований и определения цели относительно
характеристик конкретного объекта, исходя из гипотезы достаточной корректности
наличной аналитической модели (спецификации) программируемой области
исследований.
Наиболее полная последовательность этапов разработки и жизненного
цикла программ, сопровождающаяся изготовлением программной
документации, представляется следующим перечнем.
1. Постановка задачи. Определение цели, критериев и направления
обработки, ограничений на ошибки преобразований и характеристики
объектов и связей в моделях сферы исследований, типов выходных данных и
результатов, а также аналитических отношений между ними. Этап заканчивается
подготовкой технического задания на разработку программы.
22 Глава 1 Проблематика системного программирования и подбор средств для решения задач
2. Подбор языковых средств для программирования. Разработка
форматов ввода и отображения исходных данных и результатов.
3. Разработка спецификаций программ, включающих связи между
входными данными и результатами. Этот этап сопровождается выдачей проектов
эксплуатационных документов на программы.
4. Выбор метода решения задачи. Анализ возможности использования
ранее разработанного и доступного для программиста ПО. Подбор метода
обработки аналитических соотношений.
5. Разработка алгоритма решения задачи. Декомпозиция задач на
подзадачи и фрагменты. Определение последовательности решения подзадач.
Разработка структуры программы.
6. Кодирование программы средствами избранного языка
программирования. Этот этап в соединении с этапом 5 можно рассматривать как процесс
эквивалентных преобразований алгебраических формализмов в направлении
формализованной цели к языковым формализмам в виде последовательности
директив.
7. Верификация или проверка корректности программы путем
аналитического доказательства и обоснования преобразований, а также определение
ограничений на входные данные и результаты.
8. Тестирование программы. Разработка тестов и контрольных
примеров. Сопоставление реальных и ожидаемых результатов.
9. Отладка программы в случае обнаружения ошибки. Локализация
ошибок, обнаруженных в программе. Коррекция ошибок.
10. Оптимизация программы. Выбор оптимальных алгоритмов в
соответствии со структурой и составом оборудования ВК.
11. Проверка соответствия эксплуатационным ограничениям по
затратам машинного времени и памяти.
12. Разработка текстовых описаний программы и заключительных
вариантов эксплуатационных документов.
13. Исследовательская эксплуатация программ. Уточнение требований к
представлению исходных данных и результатов программы. Анализ
особенностей выполнения программы при наличии сбойных ситуаций в ВК.
14. Промышленная эксплуатация программ и коммерческое
распространение программ. Сопровождение программ и обработка требований к новым
версиям программ.
15. Обобщение и усовершенствование программ. Определение сферы
применения программ и перспективных сфер их использования.
Рассмотренные этапы разработки программ выполняются в рамках
организационных стадий в соответствии со стандартами единой системы
программной документации (ЕСПД, ГОСТ-19.102-77). Этапы 1 и 2 выполняются на
стадии разработки технического задания, этапы 3 и 4 - при эскизном
проектировании, а 5-й этап соответствует стадии технического проектирования. Этапы
6-12 выполняются на стадии рабочего проектирования, а 13-15 в порядке
внедрения программной продукции.
Язык Ассемблера в программировании информационных и управляющих систем 23
На каждом этапе используется системное ПО технологической
поддержки, что обеспечивает повышение уровня автоматизации
программирования. Для большинства инженерных задач пять первых и пять последних этапов
недостаточно формализованы и обобщены, поэтому разработка новых
программ является творческим и слабо формализованным процессом на основе
методов и приемов искусства программирования. Однако в связи с регламентацией
этого процесса стандартами ЕСПД, при разработке программ важно достичь
разумного слияния творческих этапов с регламентированными.
Основу традиционной и наиболее распространенной технологии
составления программ обеспечивают четыре наиболее важные виды программ.
Редактор текста - это программа для ввода или модификации текста,
который нужно откорректировать или запомнить во внешней памяти.
Трансляторы реализуют программы составленные на входном языке
программирования в режимах интерпретации или компиляции. Транслятор программ с языка
Ассемблера называется ассемблером, а с языка высокого уровня -
компилятором или интерпретатором в зависимости от способа реализации. Ассемблеры
и компиляторы формируют так называемые объектные модули, что позволяет
объединять программные фрагменты, составленные на разных языках.
Компоновщик или редактор связей - это программа, объединяющая выполняемую
программу с библиотечными и другими, ранее оттранслированными, модулями.
Программы-отладчики размещают выполняемую программу в памяти,
позволяют выполнять программы в режимах трассировки с отображением
результатов, отладку шаг за шагом и отладку в режиме использования адресов
отладочных прерываний. Существенной особенностью отладки системных
управляющих программ является необходимость отладки программ в режимах
аппаратных прерываний и в особых привилегированных режимах. Это усложняет
построение и работу отладчиков, и лишь некоторые из них, например, AFD,
позволяют просмотреть результаты вхождения в прерывание без возможности
продолжения отладки. Сквозная отладка программ в разных режимах
возможна лишь в таких специальных отладочных системах, как Periscope.
В процессе отладки наиболее сложной и наименее автоматизированной
задачей является установление информационной связи между отладчиками и
текстовыми редакторами для локализации ошибок. Есть перспектива развития
отладочных оболочек в направлении использования систем искусственного
интеллекта для прогнозирования логического вывода ожидаемых коррекций.
Развитие подобных идей привело к созданию метода проектирования систем с
помощью компьютера (Computer-Aided Systems Engineering) в английском
сокращении CASE Он организован на математически точной эквивалентной
декомпозиции задачи на последовательность подзадач и управляющую подзадачу
ограниченного размера в сопровождении структур данных в процессе
проектирования и коррекции ПО. Современный уровень автоматизации CASE-сис-
тем охватывает контроль разработки структур данных, коммуникацию с
другими частями программных проектов. Сейчас эта технология имеет
распространенное использование в системах автоматизированного проектирования и
разработки систем управления базами данных.
24 Глава 1 Проблематика системного программирования и подбор средств для решения задач
Краткие итоги
Программирование информационных и управляющих систем
выполняется по тем же принципам и правилам, что и программирования компьютеров
для других целей, но имеет ряд свойств, определяющих преобладание
нечисленных методов решения задач, что позволяет использовать унифицированные
структуры данных и схемы программ.
ГЛАВА 2
АРХИТЕКТУРА И СИСТЕМА КОМАНД
ПРОЦЕССОРОВ СЕМЕЙСТВА ix86
В этой главе рассмотрена структура микропроцессоре типа
ix86 и кодирование машинных команд, составляющие основу
содержательного щюграммирования на языке Ас семблера
2.1. Программно-доступные объекты процессоров
Наиболее распространенные направления использования
машинно-ориентированных языков программирования связаны с разработкой предельно
эффективных программ ОС и семантической базы реализации языков
программирования высокого уровня, а также при разработке и отладке программ для
нестандартного и современного внешнего оборудования. Аппаратные средства
типичного ВК на базе процессоров ix86 с точки зрения программиста на
Ассемблере состоят из центрального процессора (ЦП), математического
сопроцессора (МСП), главной памяти (ГП), подсистемы ввода-вывода.
Такие составные части оборудования как внутренние рабочие регистры,
кэш-память, буферы, схемы синхронизации, внутренняя и системная шины, а
также устройства управления ими, как и их конструктивное построение
остаются недоступными или незаметными для программиста на Ассемблере.
Поэтому комплексная отладка аппаратно-программных комплексов с
подключением дополнительной информационно-измерительной аппаратуры возможна
лишь после детального изучения внутреннего строения микросхем, типовых
структур и конструкций ВК. Но без знания размещения кодов команд и данных
в памяти, формируемых транслятором при обработке исходной программы на
Ассемблере, решение задач отладки аппаратуры окажется невозможным.
При решении задач по машинной программе ЦП ВК
фон-Неймановского типа считывает из ГП, декодирует и выполняет машинные команды. Для
этого он формирует вместе с управляющими устройствами последовательность
импульсов, обеспечивающую выполнение всех действий в системе, включая
арифметические и логические операции, а также обмен между ГП и внешними
устройствами. ГП хранит данные и команды в адресованных ячейках памяти,
доступ к которым осуществляется по их номеру или прямому адресу.
Совокупность всех программно доступных аппаратных средств процессора принято
называть архитектурой. При описании архитектуры базового процессора
будем использовать обозначения, принятые в языке Ассемблера, выделяя их
большими латинскими буквами, а обозначения, не используемые в Ассемблере -
маленькими. Для метаобозначений будем использовать курсив.
Адресуемые данные минимального размера базового ЦП занимают
один байт (BYTE), состоящий из восьми битов или двоичных разрядов инфор-
4LU Глава 2 Архитектура и система команд процессоров семейства ix86
мации. Более сложные данные ГП и МСП имеют такие названия и
символические обозначения типов: слово (WORD) - два байта для хранения целых чисел
и частей адреса: сегмента и смещения; двойное слово (DWORD) - четыре байта
для хранения целых и действительных чисел и полных адресов памяти; восьми-
байтные (QWORD) и десятибайтные (TBYTE) данные для целых и
действительных чисел, причем операции над данными двух последних групп типов
присущи только МСП. Все многобайтные данные и аналогичные элементы
команд хранятся в памяти так, что байты с младшими разрядами размещаются
по меньшим адресам, поэтому байты в машинном формате представляются в
обратной последовательности относительно традиционной записи данных.
Для статического определения данных перечисленных типов и
резервирования памяти для их хранения в язык Ассемблер включены директивы с
названиями, соответствующими размерам данных: DB, DW, DD, DQ, DT. Эти
директивы преобразуют символические обозначения констант, характерные
для большинства языков программирования во внутреннюю форму,
пригодную для обработки машинными командами, а также процедурами и
функциями языков программирования. Общий формат этих директив имеет вид:
Имя_области Имя_директивы Список_операндов; Комментарий
Элементы списков определяют последовательность значений. Например:
SORCAR DB 'программа1 /Фрагмент текста
DESTAR DW ?,?,?,? ;Зарезервировано 4 слова
SEG_SORCAR DW SEG SORCAR ;Сегментная часть адреса
ADDR_SORCAR DD SORCAR ;Адрес области SORCAR
PI DD 3.1415926 ;Число пи в четырехбайтном формате
Е DQ 2.7182818 ;Число е в восьмибайтном формате
RESULT DT ? /Резерв памяти для десятибайтного числа
Конкретный тип данного связан с типом директивы и определяется
инициализирующим значением. В операндах директив определения данных могут
задаваться традиционные для языков программирования численные значения
констант целого типа для всех форматов, причем для DT они преобразуются во
внутреннюю двоично-десятичную форму. Константы с плавающей точкой
записываются директивы DD, DQ и DT при условии наличия точки или
указателя порядка Е в записи константы. В общем случае операнды директив
определения констант могут задаваться выражениями, содержащими
разнообразные операции, но чаще всего они включают одиночные значения констант.
Признак типа константы обычно задается заглавной или строчной буквой в
конце со следующим смыслом или семантикой: D или пусто - десятичные; О
или Q - восьмеричные; Н - шестнадцатиричные. Директивами DW и DD могут
задаваться адресные данные, в том числе в DW внутрисегментные адреса и/или
адреса сегментов, называемые в процессорах 8086/88 селекторами, а в DD -
полные адреса ГП. Директива DB может определять символьные константы,
если в операнде заданы строки или последовательности символов,
заключенных в апострофы (' ') или кавычки (" "). Кроме того, во всех директивах допус-
Язык Ассемблера в программировании информационных и управляющих систем 27
каются неопределенные значения, задаваемые вопросительным знаком и
операции повторения в форме кратность DUP {значение).
Подсистема ввода-вывода включает различные устройства: для ввода -
клавиатуру, аналого-цифровые преобразователи, сканеры, а также кодеры
изображений и звуковой информации, для отображения и вывода - дисплеи,
принтеры, графопостроители (плоттеры), цифро-аналоговые преобразователи,
звуковые генераторы, включая музыкальные и речевые, а для информационного
обмена с другими компьютерами - сетевые адаптеры и модемы. Внешняя
память предназначена для долговременного хранения программ и данных. Она
чаще всего реализуется накопителями на магнитных дисках и лентах.
Системная шина соединяет ЦП с памятью и устройствами
информационного обмена и делится на три части: линии данных, линии адреса, линии
управления. Схемы для подключения шины к ЦП, памяти и внешним
устройствам называются интерфейсами. Взаимодействие между интерфейсом
ввода-вывода и шиной данных реализуется либо через часть адресного пространства
памяти, либо через адресованные регистры внешних устройств, называемые
портами ввода-вывода.
16-битные процессоры 8086 и усовершенствованные процессоры 80186 и
80286, имеют 16-разрядную шину данных, определяющую возможности
информационного обмена, встроенные средства, упрощающие реализацию
мультипроцессорных систем, и 20-разрядную шину адреса (для 80286 - 24-разрядную),
определяющую 1-мегабайтное адресное пространство главной памяти и
обеспечивающую возможность организации мультипрограммных режимов работы.
Модифицированный процессор 8088 при той же базовой архитектуре
отличается 8-разрядной шиной данных. Дальнейший переход к 32-разрядным
процессорам сначала привел к расширению шины адреса в ПЭВМ типа IBM/PC-AT, и
последовавшему созданию усовершенствованных процессоров 80386 и 80486,
имеющих 32-разрядные шины адреса и данных. Архитектурные и
операционные дополнения усовершенствованных процессоров сопровождаются в
дальнейшем соответствующими библиографическими ссылками [1, 2, 3, 4],
помеченными звездочками.
На рис. 2.1 показана обобщенная архитектура ЦП всего семейства
процессоров, в которых элементы базового процессора выделены двойными
линиями, а следующих модификаций - одиночными. Разрядность регистров
показана снаружи как надпись над изображением блока регистров, а номера
регистров - в фигурных скобках. Кроме того слева от изображения регистров показа- <
ны их относительные адреса при сохранении в сегменте состояния задач (TSS)
защищенного режима в 16/32-разрядных регистров.
Архитектура базовых микропроцессоров ix86 - типична для ПЭВМ IBM
PC и совместимых компьютеров. 16-битовые процессоры содержат регистр
флагов состояния f, указатель адреса текущей команды ip, две группы
16-разрядных регистров. Регистры общего назначения АХ, ВХ, СХ, DX, а также
базовые и индексные регистры ВР, SP, SI и DI, которые хранят операнды и
результаты операций и могут использоваться для дополнительных
специфических функций. Так, регистр АХ рассматривается как аккумулятор, куда удобно
помещать результаты операций, регистр ВХ используется как базовый при вы-
28
Глава 2 Архитектура и система команд процессоров семейства ix86
числении адреса, СХ - как счетчик в специальных командах, a DX - может
содержать старшие разряды входных арифметических данных и результатов
некоторых операций и содержать адрес порта ввода-вывода. В 32-битовом
режиме вместо них используются аналогичные имена регистров с предшествующей
буквой Е (extended) EAX, EBX, ECX, EDX, EBP, ESP, ESI и EDI, из которых
только ESP - не может использоваться как индексный или как базовый.
Набор программно-доступных регистров задачи
Указатель команд
Регистр флагов
Аккумулятор
Счетчик
Регистр данных
Указатель базы
Указатель стека
Базовый регистр в стеке
Индекс источника
Индекс приемника
Дополнительный
Сегмент кодов
Сегмент стека
Сегмент данных
Дополнительные
сегменты данных
Регистр локальной таблицы дескрипторов
Карта разрешения ввода-вывода
Рис. 2.1. Общая структура регистров процессоров ix86
Можно отдельно обращаться к их старшим и младшим байтам первых
четырех регистров, названных АН, ВН, СН, DH и AL, BL, CL, DL,
соответственно. Другие четыре регистра целесообразно использовать для хранения
адресов или указателей: SP - как указатель верхушки стека; ВР - базовый указатель
при работе со стеком по косвенному адресу; SI и DI - индексные регистры
источника и приемника информации, используемые для определения базы и
индекса при косвенной и индексной адресации. Последние наиболее эффективны в
командах работы со строками.
При выполнении машинной программы базовым процессором коды
получаются из ГП последовательно до тех пор, пока не встретится команда
передачи управления. Машинные коды команд программы считываются из
сегмента кода, начальный адрес которого размещается в сегментном регистре CS.
Регистр команд содержит команду на время ее дешифрации и выполнения. В
регистре IP находится смещение адреса следующей команды относительно
начала сегмента, обрабатываемой по окончании текущей команды. При передаче
20h/0Eh
24h/10h
28h/12h
2Ch/14h
30h/16h
34h/18h
38h/lAh
3Ch/ICh
40h/lEh
44h/29h
48h/22h
4Ch/24h
50h/26h
54h/28h
58h
5Ch
60h/2Ah
64h
32
EIP
EFLAGS
EAX {0}
ECX {1}
EDX {2}
EBX {3}
_ ESP {4[
EBP {5}
ESI {6}
EDI {7}
0
0
0
0
0
0
0
T 0
16
->
->
->
->
->
->
->
—>
->
->
15 0
IP
FLAGS
AH{4} AX{0} AL{0}
CH{5}CX{1}CL{1}
DH{6} DX{2} DL{2}
BH{4} BX{3} BL{3}
SP{4}
BP{5}
SI{6}
DI{7}
ES{0}
CS{1}
SS{2|
DS{3|
FS
GS
LDTR
Двоичный блок
Язык Ассемблера в программировании информационных и управляющих систем 29
управления в регистре ip размещается адрес, определяемый из кода команды
передачи управления.
2.2. Организация сегментированной памяти
Гибкость и компактность хранения адреса в машинных командах
обеспечивается наличием в базовом процессоре средств сегментации, базирования
и индексации. Сегментация позволяет указывать в командах лишь часть
адреса: смещение относительно базы, индекса и адреса начала сегмента, который
записан в одном из сегментных регистров. Содержимое сегментных регистров
усовершенствованных процессоров в защищенных (привилегированных)
режимах процессоров 80286 и 80386, называемое селекторами, указывает на
дескрипторы, которые позволяют выполнять автоматическую защиту памяти и
упрощают ее распределение между выполняемыми задачами. Эти вопросы будут
подробнее рассмотрены в следующих главах книги.
Адресное пространство процессора 8086 и реальных режимов
усовершенствованных процессоров представляется в виде сегментов до 64 Кбайтов.
Доступ к байту или слову в памяти возможен, если адресованный элемент
находится в пределах одного из четырех (в процессорах 80386, 80486 и Pentium -
шести) текущих сегментов, адресуемых сегментными регистрами. Каждый байт
в пределах сегмента адресуется с помощью шестнадцати битов адреса
(смещения). В двухбайтном слове по меньшему адресу запоминается младший байт, а
по большему - старший. Слово адресуется через указатель на адрес младшего
байта. Для определения сегмента, в котором находятся данные, используется
шестнадцатибитовый сегментный адрес, который помещается в один из
сегментных регистров. Каждый байт памяти имеет физический и
соответствующий ему логический адрес. Начальный физический адрес сегмента
определяется как сегментный адрес, умноженый на 16, а физический адрес байта в
сегменте определяется как сумма начального физического адреса сегмента и
смещения данного байта относительно начала сегмента.
Таким образом, физический, эффективный или исполнительный адрес
представляется 20-разрядным двоичным числом и используется для чтения и
записи информации в памяти. Однако в машинной программе указываются не
физические, а более короткие внутрисегментные части логических адресов,
которые обеспечивают гибкую адресацию. Логический адрес состоит из указания
на сегментный регистр и смещения (OFFSET) внутри сегмента.
Сегментные регистры CS (сегмент кода), DS (сегмент данных), ES
(дополнительный сегмент данных или экстракодов), SS (сегмент стека), а в
процессорах, начиная с 80386 еще и FS, а также GS, используются для получения 20-
битовых исполнительных адресов ГП для ЦП 8086, 8088, 80186, реального
режима 80286, а также 32-битовых исполнительных адресов реального режима
80386 и 80486 в соответствии с приведенной ниже иллюстративной схемой или
формулой
30 Гл
1ава 2 Архитектура и система команд процессоров семейства ix86
[xS]* 16+относительный адрес:
15
Сегментный адрес
0000
31
15
Расширенный [*3]
Относительный адрес
31
19
Расширенный l*3J
Исполнительный или действующий адрес
В формуле xS - обобщенный сегментный регистр, а [Тх] - его
содержимое, представляющее собой сегментный адрес. Относительный адрес в
реальном режиме определяется как сумма по модулю смещения с содержимым
базового и индексного регистров. Полное смещение в свою очередь определяется
прямым адресом или суммой 16- или 32-разрядных [*3] индексных и базовых
регистров со смещением (displacement d8, dl6 и d32 [*3]), указанным в команде.
Перенос за старший разряд полного смещения игнорируется, и действующий
адрес не может выйти за пределы 64 Кбайтов сегмента. А длинный
исполнительный адрес может сформироваться только в усовершенствованных моделях
процессоров [*3] за счет переноса в разряды сегментного адреса и
использования 32-разрядного относительного адреса. Однако следует отметить, что
реальный режим с расширенной адресацией практически не применим из-за
ограничения размера сегментов реального режима размером в 64 Кбайта, что может
быть преодолено только в процессорах фирмы AMD, совместимых с 80386 и
80486.
Таким образом 8086 оперирует 20-битовым адресом, то есть может
иметь доступ к любому из 1048576 байтов = 1 Мбайтов = 1024 Кбайтов.
Сегментация памяти процессора 8086 и усовершенствованных процессоров в
реальном режиме обеспечивает следующие преимущества:
• при использовании в командах 16-разрядных адресов можно адресовать
любой байт из 1 Мбайтов памяти;
• можно одновременно иметь доступ к нескольким 64 К-байтным
сегментам данных, стека, кода, экстракода;
• упрощается размещение программы в любых областях памяти, то есть
обеспечивается перемещаемость программ без изменения машинных
кодов внутрисегментных смещений;
• облегчается организация мультипрограммирования. Последующие
усовершенствования расширяют адресное пространство и обеспечивают
более строгую и гибкую защиту данных.
В защищенном режиме сегментные регистры содержат селектор,
используемый по схеме
Язык Ассемблера в программировании информационных и управляющих систем 31
Адрес одной из таблиц Ч
дескрипторов в регистре I 1
LDTR при 1=1 или в j I
GDTR при 1=0 )
Si
+
llillliiliiii
Трр|
000
аа...аааааааааааааа|
Адрес дескриптора сегмента
63
1
Копия - в теневом регистре
дескриптора сегмента
База (31.
.24)
48
| GDxU Предел
47
pdd
К памяти
s tttA
1
База
(23..
16)
32
1
31
1
Ваза
сегмента
(15.
•0)
16 15
1
Предел
сегмента
(15..0)
0
1
Рис, 2,2, Доступ к дескриптору сегмента
В защищенном режиме сегментный адрес выбирается из дескрипторов
как содержимое полей, обозначенных на рис. 2.2 как "База" и "База сегмента".
Относительный адрес определяется аналогично реальному, но с
использованием более разнообразных формул, которые будут рассмотрены при изучении
машинных команд. Кроме того дескриптор содержит границу сегмента,
которая проверяется при выполнении команды. Остальные разряды носят
управляющий характер и рассматриваются при изучении защищенного режима.
Регистр состояния или флагов f базового процессора ix86 используется
для управления процессом и режимами вычислений и включает 16 битов, шесть
из которых фиксируют признаки результатов выполненной операции, три -
управляют режимами работы процессора, а семь - не используются. Биты
регистра F: бит знака SF - дублирует значение старшего бита результата; значение
бита нуля ZF - равно 1 при нулевом результате; бит паритета или четности PF -
равен 1, если младший байт результата содержит парное число единиц; бит
переноса CF - равен 1, если при сложении формируется перенос из старшего бита
результата, а при вычитании заем из младшего бита старшего байта; бит
дополнительного переноса AF - равен 1, если возникает перенос из младших
четырех битов в старшие; бит переполнения OF - равен 1, когда
сформированный результат нельзя разместить в данном приемнике. Эти биты изменяются
командами арифметических и логических операций и используются командами
условной передачи управления для построения разветвленных и циклических
программ.
Биты регистра F (в 32-битовом варианте eflag), используемые для
управления режимами процессоров ix86: бит направления DF определяет
направление обработки строки; бит разрешения прерываний IF при значении 1
разрешает обработку немаскируемых прерываний; значение 1 бита трассировки TF
устанавливает режим трассировки и вызывает после каждой выполненной коман-
32 Глава 2 Архитектура и система команд процессоров семейства ix86
31 15
Резерв
заполнен нулями
А
С
V
М
R
F
INllO
хгк
UmtlcEIxE
АС - флаг проверки выравнивания [*4]
VM - виртуальный режим [*3]
RF - флаг возобновления [*3] (2 бита)
NT - флаг вложенной задачи [*2]
IOPL - уровень привилегий ввода-вывода [*2]
OF - бит переполнения
DF - бит направления автоиндексации
IF - разрешение на прерывания
TF - бит режима трассировки
SF - бит знака результата
ZF - бит-признак нулевого результата
AF - бит дополнительного ереноса
FP - бит четного количества двоичных единиц результата
CF - бит переноса из старшего разряда результата
Рис. 2.3. Структура регистра флагов FLAGS (F) и EFLAGS [*3, Ч]
ды переход на подпрограмму пошаговой отладки [25]. В усовершенствованных
процессорах [*2, *3, *4] биты IOPL определяют уровень привилегий
ввода-вывода в текущей задаче, бит NT - режим вложенной задачи, бит RM -
определяет необходимость возобновления задачи при возврате из прерывания, бит VM -
виртуальный режим выполнения программы процессора 8086, а бит АС - задает
режим проверки выравнивания данных.
2.3. Синтаксические определения записи
машинных команд
Команды ЦП рассматриваются как операционные ресурсы нижнего
уровня, доступные программисту. Использование операционных ресурсов
планируется программистом в виде последовательности машинных команд
процессора, которые в языке Ассемблера отличаются названием операции и форматами
операндов, что синтаксически определяется режимами адресации. Операнд
команды в языке Ассемблера определяется названием регистра, содержимое
которого принимает участие в операции, или выражением, обрабатываемым во
время трансляции. Это выражение может включать операции: арифметические ( +,
-, *, /, MOD), логические (AND, OR, NOT, XOR), сдвига (SHL, SHR),
отношений (EQ, NE, LT, LE, GE, GT) с традиционными приоритетами и
включенными имена меток и областей памяти для хранения переменных и констант.
Главной особенностью языка Ассемблера базового процессора по сравнению с
предыдущими аналогичными языками является использование единого названия
Язык Ассемблера в программировании информационных и управляющих систем 33
операций для всех семантически идентичных действий независимо от форматов
и размеров данных. Форматы данных определяются типом именованных
областей памяти или явными указателями в форме: название типа данных PTR
операнд из памяти .
Развитие языка Ассемблера позволило включить в
усовершенствованные версии средства для обработки новых команд и режимов новых
процессоров семейства. Для систематизации системы команд выделим базовые способы
адресации данных: сегментная регистровая s и регистровая г, когда в команде
указан 8-, 16- или 32-битовый регистр, где находятся данные. Кроме того
допускается непосредственная адресация / , когда данные есть часть машинной
команды и группа способов адресации данных в памяти т.
Последняя группа охватывает все варианты адресации памяти в
пределах доступности сегментного регистра (64 Кбайтов в реальном режиме или 4
Гбайтов в защищенном режиме, начиная с процессора 80386). Сегмент может
быть определен по умолчанию или задан явно, через имя используемого
сегментного регистра, отделенное от следующего адреса двоеточием, например,
ES: адрес. Различают 5 типов адресов.
1. Прямой 16-битовый адрес данного, как часть команды, определяемый
выражением, включающим в качестве базы название области памяти или
числовой адрес, заключенный в квадратные скобки, например ARRAY+4 или
[376Н]. В режиме 32-битовой адресации заменяется 32-битовым адресом.
2. Регистровый косвенный адрес, по которому адрес данного находится
в базовом или индексном регистре, выделенном в ассемблерной записи
команды квадратными скобками, например, [ВХ] или [DI]. В процессорах 80386 и
выше допускается использовать любой 32-битовый регистр, кроме ESP.
3. Регистровая относительная или индексная адресация, которую можно
считать расширением регистровой косвенной через добавление 8- или
16-битового смещения к содержимому базового или индексного регистра, например,
DESTAR[DI], [SI+5], SORCAR+5[BP]. В режиме 32-битового адреса вместо 16-
битового смещения используется 32-битовое, и может задаваться любой
32-битовый регистр кроме ESP.
4. Регистровый базово-индексный адрес данного вычисляется как сумма
содержимого базового и индексного регистров, определенных в команде,
например: SS:[BP][DI], где регистры ВР и DI равноправны и хранят базовый адрес
и индекс очередного объекта. В 32-битовом режиме ссылка на индексный
регистр может сопровождаться масштабным множителем индекса 2, 4 или 8, что
ускоряет индексацию данных основных типов. Например GS:[EBX+EAX*8]
вызовет обращение к двойному слову массива в сегменте GS с базой ЕВХ,
индекс или номер которого находится в регистре ЕАХ.
5. Относительный базово-индексный адрес можно считать расширением
регистрового базово-индексного адреса, который вычисляется как сумма 8-
или 16-битового смещения и содержимого базово-индексного регистра. Такой
адрес дает возможность включения в команду постоянного смещения и
использования двойных индексов, например, [BP][SI+5], DESTAR[BX][DI],
SORCAR+5[BP][SI]. Адреса 32-битового режима задаются по аналогии с 3-м и
4-м типом адресов.
2 В.И. Пустоваров
34 Глава 2 Архитектура и система команд процессоров семейства ix86
Форматы команд МП в языке Ассемблера строятся в соответствии с
двоичной внутренней машинной формой представления, по которой
обобщенный формат использует базовую двухбайтную структуру для построения
основной части команды, имеющей вид:
15
О displacement immidiate data
оооооо d/o w \mod reg/ooo r/m\ (d8/d16/d32)| (i8/i16/i32T|
Размер данных и количество следующих байтов команды определяется
конкретным кодом операции и наличием в команде фиксированных байтов
префиксов переключения разрядности данных и адресации. Здесь буквами о
обозначены биты кода операции, d - направления пересылки результатов, w -
формат данных ("О" - байт, "1" - слово или двойное слово), mod - модификация
операндов, г/т - регистр или указатель типа операнда. В машинной форме
после основной части команды с указателями модификаций адреса в памяти
может располагаться: двухбайтный адрес для прямой адресации, однобайтное d8
или двухбайтное dl6 смещение, одно- или двухбайтные непосредственные
данные i8 или il6; двухбайтное смещение и двухбайтный сегментный адрес для
прямой межсегментной адресации. Длина команд ЦП без префиксов может
достигать шести байтов. Некоторые группы команд имеют дополнительные
сокращенные форматы, которые обрабатываются несколько быстрее. Если на
код операции и режим адресации вьщелено два байта, то второй байт содержит
поля mod и r/m, а также бит w, состояние которых определяет разные режимы
адресации и назначение сегментных регистров по умолчанию. Допустимые
варианты 16-битових адресов процессоров ix86 приведены в таблице 2.1 [25] с явным
указанием сегментных регистров, используемых по умолчанию, и количества
дополнительных циклов, затрачиваемых на адресацию в процессоре 8086.
Таблица 2.1
г/т
000
001
010
011
100
101
110
111
mod-00
DS:[BX][SI]
DS:[BX][DI]
SS:[BP][SI]
SS:[BP][DI]
DS:[SI]
DS:[DI]
DS:dl6
DS:[BX]
дц
1
1
8
8
5
5
6
5
mod-01/10
DS:d8/dl6[BX][SI]
DS:d8/dl6[BX][DI]
SS:d8/dl6[BP][SI]
SS:d8/dl6[BP][DI]
DS:d8/dl6[SI]
DS:d8/dl6[DI]
SS:d8/dl6[BP]
DS:d8/dl6[BX]
дц
11
11
12
12
9
9
9
9
mod=ll
и'=0
AL
CL
DL
BL
AH
CH
DH
BH
w=l
AX
cx
DX
BX
SP
BP
s
DI
Язык Ассемблера в программировании информационных и управляющих систем 35
В таблице [регистр] - обозначает содержимое указанного регистра,
добавляемое при формировании исполнительного адреса в пределах 64 Кбайтов
используемого сегмента, а имя сегментного регистра определяет использование
сегментного регистра по умолчанию. Дополнительные циклы обращения (дц)
приведены для процессоров Intel-8086/88. В 32-битовых процессорах при
задании префикса 32-битовых данных вместо двухбайтных регистров используются
четырехбитные с дополнительной начальной буквой Е.
Комплекс префиксов и суффиксов обеспечивает гибкий выбор режимов
выполнения команд. Команды могут использовать следующие группы
префиксов, систематизированных по семантическим признакам:
а) задаваемые в виде предварительной операции:
LOCK - префикс (с внутренним кодом OFOh) для блокировки шины
данных от вмешательства другой аппаратуры на период выполнения команд обмена;
REPxx - префиксы повторения команд обработки строк со значениями
0F3h для безусловных повторений и повторений по совпадению и 0F2h - для
повторений по несовпадению:
б) задаваемые явно в операндах префиксами задания сегментов DS:, CS:,
SS:, ES:, а также FS: и GS: [*3] со значениями во внутреннем представлении
3Eh, 2Eh, 36h, 26h, 64h и 65h или неявно через директиву управления доступом
к сегментам памяти:
ASSUME сегментный регистр: закрепляемый сегмент
в) определяемые режимом трансляции и форматом операнда [*3]:
• префикс 32-разрядного адреса 67h;
• префикс 32-разрядных данных 66h.
Если режим адресации использует регистр ВР для формирования
физического адреса, по умолчанию используется содержимое сегментного регистра
SS, в других режимах адресации - DS. Если в команде используется префикс
замены сегмента, то длина основной команды увеличивается на один байт
префикса переключения сегментов, а время выполнения - на 2 цикла. Приведенные
в таблице модификации адреса присущи 16-битовым процессорам и режиму
виртуального 8086 в 80386, более разнообразные модификации адресов
реального и привилегированного режимов будут рассмотрены при изучении
построения компиляторов и средств защиты памяти в ОС.
Аналогичная замена невозможна при вычислении адреса текущей
команды, в этом случае всегда используется регистр CS. При выполнении
основных манипуляций записи-чтения со стеком с использованием регистра SP
всегда подключается сегментный регистр SS, а для адресации операнда-приемника
в командах обработки строк - используется только сегментный регистр ES.
2.4. Внутренние структуры данных процессоров
Транслятор с языка Ассемблера автоматически контролирует
допустимость операнда и генерирует коды, включая префиксы, в соответствии с задан-
т
36 Глава 2 Архитектура и система команд процессоров семейства ix86
ной моделью и режимом работы процессора для любого операнда,
указывающего на память и заданного в обобщенной стандартной форме:
Имя_области_памяти_со_смещением \имя_регистра_базы\
[имя_регистра_индекса * коэффициент ]
В этой записи может быть опущен любой из трех элементов или
коэффициент. 32-разрядный эффективный адрес формируется в соответствии с табл. 2.2
и 2.3.
Таблица 2.2
г/т
000
001
010
011
100
101
по
111
префикс 66h
mod-00
DS:[EAX]
DS:[ECX]
DS:[EDX]
DS:[EBX]
mod=01/10
DS:d8/d32[EAX]
DS:d8/d32[ECX]
DS:d8/d32[EDX]
DS:d8/d32[EBX]
управление байтом SIB
DS:d32
DS:[ESI]
DS:[EDI]
SS:d8/d32[EBP]
DS:d8/d32[ESI]
DS:d8/d32[EDI]
w=0, w=l mod =11
AL AX EAX
CL CX ECX
DL DX EDX
BL BX EBX
AH SP ESP
CH BP EBP
DH SI ESI
BH DI EDI
Здесь I - имя индексного регистра, в качестве которого нельзя
использовать только SP, a S - масштабный множитель для основных форматов данных.
Таким образом обобщенный формат команды ЦП 80386 может включать
префиксы 4-байтных данных и адресов, а также байт SIB после постбайта. Байт
SIB содержит 3-битовые поля INDEX и BASE, определяющие выбор регистров,
используемых в качестве индексного и базового, и 2-битовое поле SCALE,
задающее масштабный коэффициент для модификации значения индекса.
s s- масштаб scale:
00 =>[ii'd* 1
01 =>[iii|*2
10 =>[iii]*4
11 =>[iii]*8
Формат байта SIB:
\ss 1111 \bbb \
i i i - индексный регистр index
bbb- базовый регистр base
Если поле г/т пост-байта имеет значение 100, то используется байт SIB
и формирование 32-разрядного эффективного адреса определяется табл. 2.3.
Таблица 2.3
base
000
001
префикс 66h
mod=00
DS:[EAX+(I*S)]
DS:[ECX+(I*S)]
mod=01/10
DS:d8/d32[EAX+(I*S)]
DS:d8/d32[ECX+(I*S)]
w=0, и»=7 mod —11
AL AX EAX
CL CX ECX
Язык Ассемблера в программировании информационных и управляющих систем 37
Продолжение табл. 2.3
base
010
011
100
101
по
111
mod=00
DS:[EDX+(I*S)]
DS:[EBX+(I*S)]
SS:[ESP+(I*S)]
DS:[d32+(I*S)]
DS:[ESI+(I*S)]
DS:[EDI+(I*S)]
префикс 66h
mod=01/10
DS:d8/d32[EDX+(I*S)]
DS:d8/d32[EBX+(I*S)]
SS:dS7d32[ESP+(I*S)]
DS:d8/d32[EBP+(I*S)]
DS:d8/d32[ESI+(I*S)]
DS:d8/d32[EDI+(I*S)]
w=0, w—1 mod =11
DL
BL
AH
CH
DH
BH
DX
BX
SP
BP
SI
DI
EDX
EBX
ESP
EBP
ESI
EDI
Кодировка индексных и базовых регистров в байте SIB соответствует
стандартным номерам регистров. В этом случае длина команды может быть от
1 до 11 байт, а эффективный адрес вычисляется по формуле:
^A—(base)+{index) *scale+disp.
Так в команде: ADD ECX,TB[EDI*8] disp определется ассемблером,
index - EDI, a scale-:3.
Однако, так как вычисление адреса выполняется параллельно с другими
действиями, дополнительное время не требуется, только при совместной
обработке и BASE, и INDEX, и DISP время выполнения команды увеличивается на
1 такт.
2.5. Основные группы машинных команд и
режимы их выполнения
Машинная команда в языке Ассемблера - это оператор, представленный
в формате:
[Метка:] [Префикс] Операция Операнды ; Комментарий
Она выполняется при обработке выполняемого модуля, полученного в
результате компиляции и компоновки исполняемого модуля. В синтаксически
правильных операторах поля отделяются пробелами, а операнды - запятыми.
Семантически корректный оператор машинной команды в зависимости от ее
формата может иметь не более двух операндов. Первый операнд в команде на
языке Ассемблера используется сначала как первый аргумент операции, а
потом - как приемник результата dst, а второй операнд - только как второй
аргумент src.
Ассемблерная команда должна определять каждый операнд машинной
команды, включая режим его адресации. В двухоперандных командах
разрешены такие комбинации операндов: регистр-регистр *т; регистр-память гт;
память-регистр тг\ регистр-непосредственный операнд ri\
память-непосредственный операнд mi. Особенно следует обратить внимание на то, что типы данных
в операндах должны совпадать с типом, определенным в спецификациях ко-
38 Глава 2 Архитектура и система команд процессоров семейства ix86
манды или типом данных второго операнда команды. В последних
модификациях [*3, *4] процессоров практически все команды с операндами имеют четы-
рехбайтные модификации, использующие регистры, изображенные на рис. 2.1.
2.5.1. Операции информационных обменов
При трансляции синтаксически похожие команды порождают коды
разных форматов, например:
MOV DI,DESTAR ; Пересылка первого элемента массива
порождает команду формата гт, а соединение операторов
ODESTAR EQU OFFSET DESTAR ; Определение смещения начала
; массива
MOV DI,ODESTAR ; Пересылка начального смещения массива
порождает команду сокращенного формата ri.
ЦП 8086 имеет 92, а улучшенные ЦП еще больше типов команд, для
которых названиия или мнемокоды команд языка Ассемблера - сокращения
английских слов, соответствующих назначению команды. Команды языка
Ассемблера можно разделить на 9 функциональных групп: команды передачи данных;
команды ввода-вывода; команды манипуляций со стеком; команды передачи
флагов; арифметические команды; команды обработки бит; команды передачи
управления; команды управления процессором; команды обработки строк. В
дальнейшем в командах первых восьми групп будем обозначать операнды в
обобщенной форме: dst- приемник результата, которым может быть регистр
(reg) и операнд в виде mod - r/m; src - источник информации, которым, кроме
таких же форматов приемника, может быть непосредственный операнд /.
Первые три группы могут быть объединены в класс команд передачи
данных. Эти команды выполняют копирование или обмен данными и адресами
между регистрами и ячейками памяти или портами ввода-вывода. Они имеют
характер подготовительно-завершающих операционных ресурсов, и
используются для начальной подготовки адресов и данных, а также для сохранения или
вывода полученного результата. Эти команды, как правило, не изменяют
состояние регистра f. Базовыми командами этого класса являются:
MOV dst,svc - копирование байтов или слов из источника в приемник.
Команда допускает все возможные режимы адресации кроме пары
непосредственных операндов и пары сегментных регистров. Приемник не может быть
непосредственным операндом или регистром CS. Команда имеет сокращенный
формат при пересылке в аккумулятор.
XCHG dst,src - команда обмена содержимым двух регистров или
регистра с байтом или словом ячейки памяти. Регистры не могут быть сегментными.
Существует сокращенный формат обмена регистров с двухбайтным
аккумулятором
BSWAP reg32 - перестановка байтов в обратном порядке [*4].
Язык Ассемблера в программировании информационных и управляющих систем 39
LEA reg,src - команда загрузки относительного адреса источника в 16-
разрядный регистр. Регистр не может быть сегментным, а источник не может
быть непосредственным операндом или регистром.
LDS regySrc - команда загружает из памяти двойное слово и копирует
его первое слово в заданный регистр общего назначения, а второе - в регистр
сегмента данных DS.
LES regysrc - команда подобна LDS, но второе слово загружается в
сегментный регистр ES.
MOVSX reg,src - команда увеличения размера источника со знаковым
расширением [*3].
MOVZX reg,src - команда подобна MOVSX с расширением нулями [*3].
Рассмотрим последовательность команд:
LES SI,ADDR_SORCAR ; Пересылка полного адреса в ES:[SI]
LEA DI,DESTAR ; Загрузка внутреннего адреса сегмента
MOV DX,ES:[SI] ; Копирование данных **ерез регистр DX
MOV ES:[DI],DX ; Копирование слова в приемник.
В результате ее выполнения слово из области памяти с именем SORCAR
скопируется в область DESTAR с использованием сегментного адреса,
загруженного в регистр ES. Возможно также цикличное повторение копирования
двумя последними командами с изменением адресов в DI и SI.
Команды ввода-вывода выполняют обмен с регистрами и другим
оборудованием внешних устройств и используются для программирования
информационного обмена, контроля и управления устройствами ВК.
IN acc,prt - команда ввода байта или слова из порта в аккумулятор AL
или АХ, определенный в команде. Номер порта определяется или
непосредственно числом в диапазоне от 0 до 255, или содержимым регистра DX, что
позволяет обслуживать порты с номерами от 0 до 65535.
Именование портов может быть выполнено директивой EQU, например:
TIME_PROG EQU 4ОН ; Порт программирования системного
; таймера.
OUT prt,acc ; Копирование информации в порт.
Важную группу составляют команды информационного обмена со
стеком. Стек рассматривается как специальный тип памяти для временного
хранения данных, обмен с которым выполняется по принципу lifo: (last input - first
output или первым вводится - последним выводится). То есть стек - это память
для хранения данных с последующим считыванием их в обратном
направлении. Стек адресуется через регистр сегмента стека SS, в котором задается
сегментный адрес стека, и регистр SP, где находится относительный адрес
последнего включенного в стек элемента, называемый вершиной стека. Стек
оперирует только словами. Для работы со стеком используются команды - PUSH и
POP.
40 Глава 2 Архитектура и система команд процессоров семейства ix86
Команда PUSH загружает слово из регистра или ячейки памяти в
верхушку стека, при этом из указателя стека вычитается 2; команда POP копирует
слово из верхушки стека в ячейку памяти или регистр, при этом к содержимому
регистра SP прибавляется 2. Как правило, в программе каждой команде PUSH
соответствует своя команда POP, при этом адрес верхушки стека
восстанавливается.
PUSH src - копирует слово в сегмент стека по адресу, помещенному в
указатель стека SP и предварительно уменьшенному на 2, непосредственный
операнд для копирования адресов допускается в поздних модификациях [*1], а
в последующих [*3], еще и работа с четырех байтным и данными.
POP dst - копирует данные в обратном направлении с увеличением
содержимого SP и может восстанавливать сохраненные данные на том же месте.
Эти команды имеют сокращенный формат для сегментных регистров,
регистров общего назначения и регистра флагов F:
PUSHF - безоперандная команда, сохраняет в стеке все биты регистра
флагов F процессора в формате слова.
POPF - перемещает слово из вершины стека, адресуемое регистром SP, в
регистр флагов F. Команды PUSH и POP удобны для копирования
содержимого одного регистра в другой, например:
PUSH ES ; Копирование содержимого регистра ES в стек.
POP DS ; Копирование в регистр DS.
Недостаточная скорость сохранения и восстановления содержимого
регистров при обращении к процедурам привела к созданию групповых команд
PUSHA - сохраняет в стеке содержимое регистров общего назначения в
такой последовательности АХ, СХ, DX, BX, SP (состояние перед выполнением
команды), ВР, SI, DI.
РОРА - восстанавливает содержимое регистров, сохраненное командой
PUSH А из стека.
ENTER iw,lvl - создает стековый кадр для процедуры [*1], в котором
размещаются указатели предыдущих кадров вызовов и внутренние переменные
процедуры в такой последовательности:
• в стек записывается содержимое регистра ВР или ЕВР для 32-битового
режима, указатели кадров в количестве Ы\
• из SP вычитается величина, указанная в iw.
LEAVE - удаляет из стека очередной стековый кадр путем копирования
содержимого ВР в SP и восстановления первоначального содержимого ВР из
стека.
2.5.2. Операции информационных преобразований
Команды для арифметических операций над двоичными и
упакованными или неупакованными двоично-кодированными десятичными числами фор-
Язык Ассемблера в программировании информационных и управляющих систем 41
мируют в операнде-приемнике результат соответствующего размера.
Арифметические команды и команды работы с битами изменяют состояние регистра F
в соответствии с полученными результатами по схеме, приведенной ниже.
Арифметические команды и команды битовых преобразований [4].
В таблице использованы обозначения: и*" - обозначает изменение бита
признака; "-" - обозначает сохранение его состояния; и0" или "1" - установка
бита признака независимо от результата; "?" - негарантированное значение бита
признака.
Таблица 2.4
Название
операций
ADD,ADC
SUB,SBB
CMP
XADD*4
CMPXCHG *4
INC,DEC
NEG
MULJMUL
DIVJDIV
DIVJDIV
AAA,AAS
DAA,DAS
AAM,AAD
CBW,CWD
CDQ*3
AND,OR,XOR
TEST
NOT
ВТ, BTC *3
BTR,BTS *3
BSF,BSR *3
SAL/SHL,SAR
SHR
ROL,ROR
RCL,RCR
SHLD,SHRD*3
Формат
операндов
dst,src
dst,src
dst,src
dst,src
dst, src
dst
dst
src
src
src
dst,src
dst,src
dst
reg,inx
reg,inx
dst,src
dst,cut
dst,cnt
dst,cnt
dst,cnt
dst, src, с
Флаги условий
OF SF ZF AF PF
CF
*
*
*
*
*
*
*
?
?
?
?
7
-
-
0
0
-
7
7
-
*
*
*
*
*
* *
* *
* *
* *
* *
* *
* *
7 7
? 7
7 7
7 ?
* *
* *
- -
- -
* *
* *
- -
7 7
7 7
*
* *
0 *
- -
- -
0 *
* *
* *
* *
* *
* *
* *
* *
7 7
7 7
7 7
* 7
* *
9 *
- -
- -
7 *
7 *
- -
7 7
7 7
- -
7 *
7 *
- -
- -
7 *
*
*
*
*
*
-
*
*
7
7
*
*
7
-
-
0
0
-
*
*
-
*
*
*
*
*
Циклы выполнения
rr 86 88 rr *2 *3 Hrm
3 16 24 2 7/7
3 16 24 2 7/7
3 9 13 2 7/6
_ _ _ _ _
2 16 24 2 7/7
3 16 24 2 7/7
70-77
80-90
80-90
4
4
83/60
2/5
3 16 24 2 7/7
9 13 2 7/6
16 24 2 7/7
. _ _ _
_ _ _ _ _
_ _ _ _ _
2/8+4*[cll
2/8+4*fcll
2/8+4*[cl]
2/8+4*[cll
2/8+4*[cll
7/6 1/3
7/6 1/3
5/6 1/2
- 3/4
6/7-10
7/6 1/3
7/6 1/3
13-42
40-40
43-44
3
2
14
3
3
7/6 1/3
5/6 1/2
7/6 1/3
3/8
6/13
6-42
3/4
3/4
3/4
3/4
2/3
Команды с операндами могут обрабатывать как байты, так и слова и
разрешают все режимы адресации, а результат в общем случае размещается в
операнде dst. Операнд типа cut (счетчик) определяется как CL или 1.
42 Глава 2 Архитектура и система команд процессоров семейства ix86
ADD просто суммирует содержимое операндов, a ADC дополнительно
еще прибавляет значение бита переноса CF. В команде XADD [*4], кроме
суммирования в приемнике начальное значение приемника запоминается в
источнике. SUB вычитает от содержимого операнда-приемника содержимое
операнда-источника, a SBB еще вычитает значение бита CF из младшего разряда
результата. СМР сравнивает содержимое приемника и источника через
вычитание, результат которого не сохраняется. В бите CF при сложении формируется
перенос из старшего бита результата, а при вычитании - заем из младшего
бита старшего байта, что позволяет использовать команды ADC и SBB для
реализации многобайтных операций.
NEG - команда формирования знаковой инверсии.
INC и DEC - команды увеличения и уменьшения на 1 содержимого
операнда-приемника.
ААА и DAA - команды коррекции результата команды сложения
десятичных чисел в регистре AL. Команда ААА преобразует содержимое регистра
AL в одну неупакованную десятичную цифру, а команда DAA - в две
упакованные десятичные цифры. AAS, DAS - аналогичные команды коррекции
результата вычитания десятичных данных в аккумуляторе AL.
MUL множитель и IMUL множитель выполняют беззнаковое и
знаковое умножение в дополнительном коде содержимого аккумулятора на
множитель с размещением произведения в расширенном аккумуляторе При
умножении байтов, содержимое AL используется как множимое, а слово результата
загружается в регистр АХ. При умножении слов содержимое АХ используется
как множитель, а двойное слово результата размещается в регистрах DX, АХ.
Если значимые разряды попадают в старшую половину результата, биты CF,
OF устанавливаются в 1, а иначе равны 0.
DIV делитель и IDIV делитель - беззнаковое и знаковое деление
содержимого расширенного аккумулятора на делитель. При делении частное
формируется в AL для байтов или в АХ для слов, а остаток - в АН или DX. Если
значение частного не помещается в соответствующем аккумуляторе или при
делении на 0, происходит прерывание.
CBW, CWD и CDQ [*3] расширяют знаковый разряд делимого в
старшие разряды расширенного аккумулятора для байтов, слов и двойных слов.
Используются непосредственно перед командами деления с учетом знаков
IDIV.
ААМ - команда коррекции результата предыдущей команды
умножения десятичных неупакованных операндов в регистре АХ.
AAD - команда замены числителя в регистре AL перед делением
десятичных неупакованных операндов так, чтобы частное от деления было также
десятичным неупакованным числом.
Базовый процессор имеет три группы команд для обработки битов в
форматах байта или слова: логические и сдвига.
AND - поразрядное логическое умножение или конъюнкция.
OR - поразрядное логическое сложение или дизъюнкция.
XOR - поразрядное сложение по модулю 2.
Язык Ассемблера в программировании информационных и управляющих систем 43
NOT - инвертирует все двоичные разряды операнда.
TEST - поразрядная конъюнкция без фиксации результата.
Команды ВТ, ВТС, BTR и BTS [*3] - копируют содержимое бита
первого операнда, номер которого указан во втором операнде, в бит CF, а затем
ВТС изменяет этот бит, BTS и BTR - устанавливают этот бит в "1" и "О".
Команды BSF и BSR [*3] - формируют в операнде-приемнике номер
первого ненулевого бита при просмотре операнда-источника с младших и старших
разрядов. Номера формируются, считая номер младшего бита нулевым. При
нулевом операнде устанавливается ZF = 1.
Сдвиги битов в байтах или словах могут быть арифметическими,
логическими или циклическими. При арифметических сдвигах вправо знаковый бит
расширяется в освобождающиеся старшие разряды при арифметическом сдвиге
влево Изменение состояния старшего бита устанавливает в 1 флаг
переполнения OF. Арифметические сдвиги используются для умножения и деления
двоичных чисел на степень двойки, а логические - для выделения группы битов в
приемнике. Первые буквы мнемоники SA определяют арифметический сдвиг, SH -
логический, RO - циклический, RC - циклический через бит переноса CF.
Последняя буква в мнемонике команды указывает направление сдвига: L - влево, R -
вправо. Счетчик может быть указан как константа 1 или как регистр CL,
поэтому максимальное количество сдвигов 255. Бит CF всегда сохраняет значение
последнего сдвинутого за пределы приемника бита. В этом описании знак "/"
отделяет синонимы - мнемоники в описаниях команд. SAL/SHL - логический и
арифметический сдвиг операнда-приемника влево на количество бит,
определяемое счетчиком, биты справа заполняются нулями. SHR - сдвигает приемник
аналогично предьщущим командам, но вправо. SAR - сдвиг вправо на число
разрядов, определяемое счетчиком, с расширением знакового бита в освобожденные
слева разряды. ROR, ROL - поразрядный циклический сдвиг вправо и влево в
приемнике через непосредственно связанные старший и младший разряды.
RCR, RCL - аналогичные сдвиги, где бит CF является связывающим
расширением операнда.
SHLD и SHRD [*3] - объединяет первые два операнда во внутреннем 8-
байтном регистре и выполняет сдвиг либо на количество битов, указанное в
CL, либо на количество битов, указанное в дополнительном однобайтном
операнде. 4-байтный результат помещается в операнд-приемник.
2.53. Базовые операции управления
Команды передачи управления изменяют содержимое регистров CS и IP,
в результате чего процессор переходит на новую последовательность команд.
Эти команды делятся на 4 группы: команды безусловной и условной передачи
управления, вызов процедур и прерываний.
Тип передачи управления при безусловных переходах определяется
типом операнда или указанными перед адресом перехода ключевыми словами
NEAR PTR (внутрисегментный прямой), FAR PTR (межсегментный прямой),
WORD PTR (внутрисегментный косвенный) и DWORD PTR (межсегментный
нгнг Глава 2 Архитектура и система команд процессоров семейства ix86
косвенный). Такие форматы имеют команды безусловной передачи управления -
JMP и вызова процедуры - CALL. Адрес перехода может помещаться
непосредственно в команде, или в регистре, или в ячейке памяти, указанных в команде.
Внутрисегментная команда JMP с прямой адресацией прибавляет к
содержимому регистра IP указанное в команде 8- или 16-битовое смещение. При
косвенном JMP в регистр ip копируется 16-разрядный адрес перехода из регистра или
из памяти по указанному адресу. Межсегментный JMP с прямым адресом
загружает в регистры IP и CS четырехбайтный адрес, указанный в команде.
Межсегментные косвенные JMP и CALL могут функционировать только через
адрес входной точки, записанный в памяти. Первое слово адресованного
командой четырехбайтного указателя загружается в регистр IP, а второе - в CS.
Межсегментный переход в защищенном режиме использует в селекторах
указатели на дескрипторы сегментов, в качестве которых могут использоваться
системные дескрипторы шлюзов или сегментов состояния задач TSS. При этом
смещения, заданные в операнде команды или в поле адреса перехода,
игнорируются. Поле типа дескрипторов определяет конкретный тип и способ
выполнения команды перехода.
Команды условной передачи управления проверяют текущее состояние
регистра F. Если условие, указанное в мнемонике команды выполняется, то
управление передается на команду по указанному адресу, иначе сохраняется
текущая последовательность команд. Адрес начала новой последовательности
команд формируется как сумма текущего значения IP с однобайтным знаковым
смещением, определяемым вторым байтом команды. Это ограничивает
диапазон условного перехода в пределах -128.. +127 байтов относительно начала
следующей команды. Во внутреннем представлении в первом байте
используется код операции, в котором старший полубайт 0111Ь определяет операцию
условного перехода, а младший - двоичный код условия сссс. В
усовершенствованных процессорах [*3] имеется четырехбайтная команда с двухбайтным
кодом операции расширенным до двух байтов относительным адресом перехода
[4]. Полный перечень команд условной передачи управления приведен в табл. 2.5.
Таблица 2.5
Мнемоника/двоичный код
условия
JC/ib/inae
JE/JZ
JNC/jae/jnb
JNE/JNZ
JNO
JNP/JPO
JNS
JO
JP/JPE
JS
cccc
0010
0100
ООП
0101
0001
1011
1001
0000
1010
1000
^ ew; if J^CJloeue передачи управления
^ v Аналитическое
CF=1
ZF=1
CF=0
ZF=0
OF=0
PF=0
SF=0
OF=1
PF=1
SF=1
Текстовое содержание
перенос
равно или ноль
нет переноса
не равно или не ноль
нет переполнения
нет паритета
знак "+"
переполнение
паритет
знак "-"
Язык Ассемблера в программировании информационных и управляющих систем 45
Продолжение табл. 2.5
Мнемоника/двоичный код
условия
JAE/JNB/jnc
JB/JNAE/jc
JA/JNBE
JBE/JNA
JG/JNLE
JGE/JNL
JL/JNGE
JLE/JNG
JCXZ
LOOP
LOOPE/LOOPZ
LOOPNE/LOOPNZ
cccc
0011
0010
0111
0110
1111
1101
1100
1110
Условие передачи управления
Аналитическое
CF=0
CF=1
(CForZF)=0
(CForZF)=l
(SF xor OF)or ZF=0
(SF xor OF)=0
(SFxorOF)=l
(SFxorOF)orZF=l
CX = 0
CX--;CX\=0
CX«;CX\=0 and ZF\=0
CX-;CX\=0 and ZF =0
Текстовое содержание
не ниже
ниже
выше
не выше
больше
не меньше
меньше
не больше
ноль в счетчике
цикл
цикл, если ноль
цикл, если не ноль
В мнемониках буквами А и В обозначают "выше" и "ниже" и
предназначены для использования при сравнении беззнаковых величин, а буквами G
и Н - "больше" и "меньше" - для сравнения знаковых. Аналогичная группа
команд в процессорах, начиная с i386, используется для условной установки
данных командами группы SET (табл. 2.6). Команды циклов используются для
организации программных циклов, использующих в качестве счетчика
содержимое регистра СХ, который должен быть инициализирован числом
повторений в цикле. Ограниченный диапазон адреса перехода вызывает
необходимость использования одновременно команды безусловного перехода,
передающие управление как в пределах текущего сегмента кода (внутрисегментный
переход), так и за его пределы (межсегментный переход).
Таблица 2.6
Мнемоника
SETO
SETNO
SETB/SETNAE
SETC
SETAE/SETNB
SETNC
SETE/SETZ
SETNE/SETNZ
SETBE/SETNA
SETA/SETNBE
SETS
SETNS
cccc
0000
0001
0010
0010
ООП
ООП
0100
0101
оно
0111
1000
1001
Условие установки
Флаги
OF=1
DF=0
CF=1
CF=1
CF=0
CF=0
ZF=1
ZF=0
CForZF=l
CF or ZF=0
SF=1
SF=0
Текстовое содержание
переполнение
нет переполнения
ниже
перенос
выше или равно
переноса нет
равно нулю
не равно нулю
ниже или равно
выше или не ниже
минус, отрицательное
плюс, положительное
4О Глава 2 Архитектура и система команд процессоров семейства ix86
Продолжение табл. 2.6
Мнемоника
SETP/SETPE
SETNP/SETPO
SETL/SETNGE
SETGE/SETN1
SETLE/SETNG
SETG/SETNLE
сссс
1010
1011
1100
1101
1110
1111
Условие установки
Флаги
PF=1
PF=0
SFxorOF=l
SF xor OF=0
(SF xor OF) or
ZF=1
(SF xor Off) or
ZF=0
Текстовое содержание
четность
нечетность
меньше или не больше
не меньше или больше
меньше или равно
больше
Команда CALL сохраняет адрес возврата в двухбайтный (NEAR) или
четырехбайтный (FAR) формат. Для корректного возврата из процедуры тип
команды RET (NEAR или FAR) должен соответствовать типу CALL,
обеспечивающей обращение к процедуре и формирующей двух- или четырехбайтный
адрес возврата, загруженный в стек соответствующей командой CALL.
RET [nmb] - синтаксис команды возврата из процедуры, которая
возвращает управление команде, следующей за командой CALL, где nmb
необязательный операнд, определяющий дополнительное смещение регистра SP,
прибавляемое к нему в конце выполнения команды. Этот операнд позволяющий
балансировать стек при извлечении из стека ненужных аргументов процедур.
Команда RET копирует из стека слово, адресуемое регистром SP, в регистр IP,
а в случае межсегментной RET в регистр CS копируется еще и следующее слово
из стека. Тип команды RET определяется типом процедуры, в которой она
использована.
Команды прерываний рассматриваются как специальные
межсегментные обращения к процедуре по коственному адресу, указанному в
соответствующем векторе прерывания, размещенному в начале памяти ВК. Команды
прерываний можно использовать в программах и генерировать похожие
аппаратные прерывания оборудованием ВК. При программном и немаскируемом
(NMI) вызове прерывания ЦП работает по укороченному циклу.
Команда INT vet, где vet номер вектора прерывания, обращается к
процедуре обработки прерывания с указанным номером. При этом перед
фиксацией межсегментного адреса возврата в стек копируется содержимое регистра
флагов F, а его биты TF, IF устанавливаются в ноль. Адрес указателя
процедуры обработки прерываний вычисляется как vet * 4 и указатель копируется
соответственно в регистры IP и CS. Для команды INT 3 ассемблер генерирует
однобайтный код, который обычно используется для останова программы в
процессе отладки. Программные прерывания могут использоваться как "вызовы
супервизора", для запроса сервисных функций ОС, например, процедур,
написанных для обработки аппаратных прерываний.
Команда INTO - условное прерывание при наличии переполнения (OF=
1), использующее вектор 4.
Язык Ассемблера в программировании информационных и управляющих систем 47
IRET - команда возврата в прерванную программу. Она возобновляет
прерванные вычисления путем загрузки регистров IP, CS и регистра F
значениями, записанными в стек последней командой прерывания. IRET
используется для выхода из процедуры как для программных, так и для аппаратных
прерываний.
2.5.4. Групповые операции
Наиболее мощными командами процессора являются 5 базовых команд-
примитивов обработки строк, которые оперируют одноэлементными
последовательностями байтов, слов или двойных слов. Последовательность входных
кодов размещается в текущем сегменте данных по адресу в регистре SI, а адрес
приемника последовательности кодов - в регистрах ES и DI. Эти регистры
должны быть загружены перед выполнением команды обработки строк, для
чего используются команды LDS, LES, LEA. Команды обработки строк
автоматически инкрементируют (при DF=0) или декрементируют (при DF=1) на
длину элемента содержимое регистров SI и/или DI, используемых в команде, для
подготовки обработки следующего элемента строки.
Команды этой группы чаще используются в безоперандной форме,
тогда тип элементов определяется дополнительной последней буквой мнемоники
команды [*3]: В - определяет байт, W - слово, a D - двойное слово.
LODS src - загружает элемент источника в аккумулятор.
STOS dst - копирует содержимое аккумулятора в строку-приемник.
MOVS dstysrc - копирует элемент строки-источника в строку-приемник.
CMPS src,dst - сравнивает строки вычитанием элемента
строки-приемника из элемента строки-источника.
SCAS dst - команда сканирования, сравнивающая строки вычитанием
элемента строки-приемника из аккумулятора. Две последние команды
модифицируют биты AF, CF, OF, PF, SF, ZF регистра F, используемые в командах
условного перехода и в однобайтных префиксах повторения, указанных перед
базовыми условными командами. При выполнении команд с префиксом регистр
СХ используется как счетчик и должен быть предварительно загружен
предельным количеством повторений, а затем его содержимое уменьшается на 1
после каждого повторения команды. Максимальная длина строки 64 Кбайтов -
1. Префикс REP обеспечивает повторение команды до значения СХ=0, a REPE
и REPZ кроме этого требуют, чтобы ZF = 0 для следующего повторения. Если
начальное значение СХ=0, то команда не выполняется ни разу. REPNE,
REPNZ обеспечивают повторение лишь при ZF=0, иначе происходит переход
на следующую команду. Регистры СХ, SI, DI сохраняют значение,
сформированное после выполнения последней элементарной команды.
Команда CMPXCHG [*4] сравнивает вычитанием операнда приемника
из аккумулятора и при совпадении корректирует приемник значением
источника, и в противном случае аккумулятор значением приемника.
INS - ввод данных [*1] из порта, указанного в DX в область памяти,
адресуемую ES:[DI];
48 Глава 2 Архитектура и система команд процессоров семейства ix86
OUTS - вывод данных [*1] в порт, указанный в DX из DS:[SI]. Эти
команды также используются с автоматическим инкрементом и декрементом [*1]
и могут употребляться вместе с префиксом REP.
Если простые команды мы будем при программировании рассматривать
как базовые операционные ресурсы программиста, то сложными командами, и
групповыми фрагментами определяются сложные операционные ресурсы.
Например, групповые команды можно специфицировать таким образом:
REP MOVSB ; Пересылка данных между областями памяти.
REP STOSB ; Запись постоянного байта в область памяти.
REPZ CMPSB ; Сравнение байтовых строк.
REPZ SCASB ; Пропуск однообразных кодов.
REPNZ SCASB ; Поиск байтов по значению.
Кроме команд работы со строками есть еще и две специальные
групповые операции.
BOUND reg,src [*1] - сравнивает первый операнд в формате слова или
двойного слова с границами, указанными в первом и втором элементах
второго операнда (словах или двойных словах); при выходе значения за пределы
диапазона возникает прерывание по вектору 5.
Преобразование числовой и символьной информации выполняется
командой перекодирования с таким синтаксисом:
XLAT ftblj- используется для трансляции символов из одного кода в
другой.
При выполнении команды байт в регистре AL замещается байтом из
созданной пользователем 256-байтной таблицы трансляции. Предполагается,
что регистр ВХ содержит адрес начала этой таблицы. Содержимое AL
определяет смещение байта-результата перекодирования, который пересылается в
регистр AL (первый байт таблицы имеет смещение 0).
2.5,5, Средства управления и организации вычислений
Безоперандные команды управления микропроцессором позволяют
программно изменять режимы функционирования ЦП путем манипуляций битами
регистра флагов f и управления вычислительным процессом.
LAHF - копирует содержимое регистра флагов F в регистр АН.
SAHF - сохраняет содержимое АН в регистре флагов F и используется
для передачи значений флагов из МСП в ЦП.
CLC - команда установки бита переноса CF в 0.
STC - бит переноса CF устанавливается в 1.
CMC - инвертирует бит флага переноса CF.
Команды CLC, STC, CMC часто используются вместе с командами
циклических сдвигов. CLD устанавливает бит направления DF в 0, а команда STD -
в 1. CLI устанавливает бит разрешения прерывания IF в 0, после этого процессор
не воспринимает внешние запросы на прерывания, формируемые на линии intr,
Язык Ассемблера в программировании информационных и управляющих систем 49
то есть блокируются маскируемые прерывания. STI устанавливает бит IF в 1, и
разрешает обработку маскируемых прерываний. Однако прерывание не будет
приниматься на обработку, пока не выполнится следующая за STI команда.
Для внешней синхронизации процессора используется команда останова
HLT, выход из состояния останова происходит при получении сигналов
немаскируемых прерываний или при перезапуске процессора сигналом reset. При
выполнении команды NOP процессор лишь изменяет содержимое регистра IP.
Команда WAIT переводит процессор в состояние ожидания, которое сохраняется,
пока по линии test не поступит сигнал окончания операции с математическим
сопроцессором (МСП).
Префикс LOCK может использоваться для блокировки обращений к
основной памяти другим оборудованием на период выполнения следующей
команды, что позволяет успешно синхронизировать параллельное
взаимодействующие процессы. Этот префикс разрешается только перед арифметическими и
логическими операциями, модифицирующими память, и перед командами
обмена с памятью XCHG, а также перед командами логических операций и
командами проверки и изменения битов.
Системные команды защищенного режима будут рассмотрены в
разделе, непосредственно связанном с составлением управляющих программ.
Краткие итоги
Язык Асслера позволяет программисту непосредственно использовать
такие атомарные ресурсы как регистры и элементарные операции, что создает
предпосылки для их эффективного использования.
ТЕХНИКА МОДУЛЬНОГОПРОГРАММИРОВАНИЯ
НА ЯЗЫКЕ АССЕМБЛЕРА
В этой главе jxiccMompenn методика модульного
программирования с использованием средств языка Ассемблера. Она
позволяет организовать взаимодействие программ, составленных на
]Х1зных языках, например, штолг>зовать в Ассемблер средства
ввода-вывода языков высокого уровня при условии загрузкл
среды этого языка. Пользуясъ матщпишти главы можно шюы-
сшпь эффектштость щю/^тммы за счет ассемблерных
вставок э(/х/)екпшвных команд и групп команд на машинном уровне.
3.1. Базовые директивы для оформления модулей
на языке Ассемблера
Язык Ассемблера IBM PC совместимых компьютеров - это машинно-
ориентированный символический язык программирования, в котором
символические обозначения в большинстве случаев относятся к машинным объектам:
памяти, элементам архитектуры процессора (регистры) и структуры ЭВМ
(порты внешних устройств). Машинные команды задаются соответствующими
операторами, то есть каждому оператору соответствует одна машинная команда.
Программист на Ассемблере может использовать все возможности системы
команд ВК, но, в отличие от использования языков высокого уровня, вынужден
брать на себя ответственность за эффективность и корректность программ и их
связей с другими программными модулями ВК. Кроме того, он часто должен
предусмотреть возможность одновременного выполнения нескольких
вычислительных процессов по одной копии выполняемых кодов (так называемая реен-
трантность или реентерабельность) модулей и динамическое выделение блоков
памяти для их выполнения.
Язык Ассемблера семейства процессоров ix86/87 ориентирован на
построение блочно структурированных программных модулей, пригодных для
использования в любом другом языке программирования при условии
выполнения стандартных соглашений по межмодульным связям. Одна часть блоков
определяет прототипы фрагментов программ (макроопределения и
структурные блоки), а другая - позволяет систематизировать элементы программы.
Программа на Ассемблере оформляется как совокупность блоков,
составленных из операторов: директив и команд. Основные принципы записи машинных
команд в Ассемблере рассмотрены в предыдущем разделе. В этой главе мы их
только уточним с позиций явного и неявного задания .адресов данных и
сегментов в командах, а также рассмотрим базовые директивы для Ассемблера
фирмы Microsoft. В дальнейшем будем отмечать особенности Ассемблеров фирм
Язык Ассемблера в программировании информационных и управляющих систем
51
Intel и Borland, которые в основном совместимы с Ассемблером фирмы
Microsoft.
Отметим, что большинство современных компиляторов с языков
высокого уровня имеют специальные операторы ассемблерных вставок, которые
позволяют сочетать преимущества программирования на языке высокого уровня
с эффективностью локальных фрагментов программ. Но для этого необходимо
знать принципы пользования компилятором машинных объектов, а также
учитывать возможное понижение эффективности автоматической оптимизации в
этих условиях.
Директивы или псевдооператоры, используемые для определения
форматов данных и их значений, записывают в формате:
Имя объекта Мнемоника директивы Операнды ; Комментарий
Алфавит Ассемблера включает все латинские буквы, при этом
заглавные и прописные буквы являются эквивалентными, цифры от 0 до 9,
специальные знаки: ? @ _ $ & и разделители:
Лексемой называют неразделимую последовательность символов,
имеющую смысл для Ассемблера. Конструкции Ассемблера формируются из
идентификаторов, определяющих имя программных объектов, констант и
ограничителей. Идентификатор представляет собой набор букв, цифр и символов _, ?, $, @,
который не должен начинаться цифрой. Знак денежной единицы $ используется
для получения текущего адреса в текущем сегменте. Идентификатор должен
полностью размещаться в одной строке текста. Идентификаторы отделяются
пробелом или разделителем, которым является любой недопустимый в
идентификаторе символ. Идентификаторами представляются метки, переменные, то
есть названия областей памяти, где хранятся значения переменных, а также
имена других объектов ВК и программы. Идентификаторы, имеющие
предопределенный смысл и используемые только в соответствии с ним, называются
ключевыми словами. К ним относятся директивы Ассемблера, инструкции
процессора, имена регистров, операции выражений.
Имена меток и переменных определяют объекты этапа выполнения
программы и идентифицируют команды или данные, хранимые в памяти, и имеют
три атрибута, которые могут использоваться программистом через ключевые
слова специфических операций одной из следующих форм: SEG имя - адрес
сегмента, в котором определено имя переменной; OFFSET имя - смещение
адреса поля памяти относительно начала сегмента; TYPE имя - тип определяет
количество байтов, равное длине одиночного данного для переменной, а для
меток - отрицательной длине адреса обращения, разделенной на 2. Операции
LENGTH имя и SIZE имя возвращают соответственно значения количества
единиц данных и байтов, которые определены в первом операнде
соответствующей директивы. Так, например, для области памяти с именем dblArray,
зарезервированной для хранения десяти четырехбайтных данных выражения
LENGTH dblArray и SIZE dblArray возвратом значения 10 и 40. Для изменения
52 Глава 3 Техника модульного программирования на языке Ассемблера
атрибута типа переменной или метки на новый используется операция замены
атрибута типа - тип PTR в соединении с ключевыми словами типов.
Именами считаются идентификаторы, определенные директивой EQU,
операндом которой является совокупность букв, число или любое сложное
многокомпонентное выражение синтаксически допустимое в языке Ассемблера.
Переменные этапа компиляции определяются и изменяются специальной
директивой, в которой вместо EQU употребляется знак равенства "=".
Комбинация имен и числовых данных, составленная с использованием знаков операций,
называется выражением. Каждый из операндов выражения называют термом.
В языке Ассемблера для определения выражений используются пять групп
операций: арифметические, сдвига, логические, сравнения, выделения старшего
(HIGH) и младшего (LOW) байтов. Если базовый элемент операнда - имя
переменной или метки, выражение является адресным.
Концепция данных в Ассемблере построена так, чтобы:
• обеспечить обработку данных, переданных из внешних программ;
• использовать статически размещенные в памяти константы и
переменные с определенными и неопределенными начальными значениями;
• использовать динамически выделенную память для размещения
промежуточных результатов вычислений.
Статически подготовленные данные или статически зарезервированная
для них память, размещается в сегментах данных программы Ассемблера, или
в структурных блоках. В них, как правило, используются директивы DB, DW,
DD, DQ и DT для байтов, слов, двойных слов, восьми- и десятибайтных
данных. По сравнению с языками высокого уровня важно подчеркнуть, что в
большинстве версий языков Ассемблера тип данного определяется только для
области памяти без учета его синтаксической структуры.
Транслятор с Ассемблера обеспечивает правильное распознавание и
трансляцию во внутреннюю форму знаковых и беззнаковых данных с
фиксированной и плавающей точкой, а также адресных данных допустимых в
вычислительной системе. Однако в отличие от языков высокого уровня со строгой
типизацией данных ответственность за семантическое соответствие
использования данных семантике команд возлагается на программиста. Так, в
Ассемблерах семейства процессоров ix86 работа с адресными данными обеспечивается
либо специальными командами манипуляции адресами, либо специальными
операторами выражений OFFSET и SEG, формирующими внутрисегментные
смещения и сегментные адреса. Работа с целочисленными данными и данными
с плавающей точкой разделяется на уровне мнемоник или названий машинных
операций, а работа со знаковыми и беззнаковыми целочисленными данными
различается при умножении и делении, а также на уровне команд условных
переходов по результатам выполнения этих команд и на уровне способа контроля
переполнений результатов.
Форматы арифметических данных соответствуют традициям языков
программирования высокого уровня, где все возможные типы арифметических
данных, обрабатываемых во внутренней форме, делятся на такие группы:
• двоичные целые без знака или со знаком;
Язык Ассемблера в программировании информационных и управляющих систем 53
• упакованные и неупакованные десятичные без знака;
• вещественные.
Двоичные числа, предназначенные для обработки центральным
процессором типа ix86, могут быть 1-, 2- или 4-байтными. Диапазоны допустимых
значений определяются возможностями разрядной сетки в соответствии с
размером константы. Диапазон допустимых беззнаковых целых положительных
чисел - 0.. 2"-1, а для целых чисел со знаком —2п-|..2п-|-1, где п - разрядность
представления числа. Для работы математического сопроцессора (МСП)
используются 4-. 8- и 10-байтные форматы данных, на которых подробнее
остановимся при изучении программирования сопроцессора.
Отметим, что базовой формой внутреннего представления данных
центрального процессора является двоичная. Упакованные десятичные данные
рассматриваются как вспомогательные, или как данные специального
назначения. Для них не существует специальных форматов центрального процессора и
целесообразно определять двоично-десятичными байтами (Binary Coded
Decimal - BCD) в форме шестнадцатиричных цифр:
PACKED DB 78h,56h,34h,12h ;
Так определяется упакованное десятичное число 12345678. Для
обработки двоично-десятичных чисел применяется последовательный байтовый режим
с последующей двоично-десятичной коррекцией результата двоичной операции
специальными командами.
В директивах определения констант используются только данные типов,
обрабатываемых в ЦП и МСП: целые, вещественные и десятичные числа, а
также символьные и строковые константы. В выражениях, командах и
директивах языка Ассемблера используются только целые константы. При наличии
более сложных моделей программист может использовать структурные блоки,
записи и структуры. Такие специфические данные, которые требуют
индивидуальное кодирование, как множества и перечислимые данные в языках Pascal
или С, могут быть построены с использованием сложных макросредств.
Целые числа, обрабатываемые базовым процессором в Ассемблере,
имеют синтаксис: цифры\цифры R, где R - буква-признак системы счисления.
Латинская прописная или заглавная буква определяет основу системы счисления
числа: В - двоичную, Q i О - восьмеричную, D - десятичную, Н -
шестнадцатиричную. Шестнадцатиричные числа должны начинаться с цифры меньшей 10
для различия с идентификаторами. Базовая форма целого числа использует
основание по умолчанию, которая может устанавливаться директивой .RADIX, a
иначе используется режим десятичных данных. Как уже отмечалось, числа
могут быть положительными или отрицательными.
Вещественные числа, обрабатываемые МСП, специальными
библиотечными программами и программными эмуляторами, имеют синтаксис:
цифры R|[+ |- ]цифры.цифры[Е [+\-]цифры
54 Глава 3 Техника модульного программирования на языке Ассемблера
Первый формат используется для отображения шестнадцатиричного
вещественного числа во внутренней форме. Действительное число может также
кодироваться последовательностью шестнадцатиричных цифр. Каждое число
имеет знак, смещенный порядок экспоненты и мантиссу, которые хранятся как
поля битов числа. Вторым форматом задается вещественное число с
плавающей запятой в десятичной форме. Такие числа могут использоваться в
директивах DD, DQ, DT. Упакованные десятичные числа представляются так же, как
целые десятичные числа, но могут иметь знак, и определяются только
директивой DT.
Символьные и строковые константы имеют синтаксис: 'символы* или
"символы" и определяются только директивой DB. Символьная константа
содержит один символ и может использоваться как непосредственный операнд
машинной команды, а строковая - 2 и больше символов. В исходном тексте
программы числовые и символьные лексемы представляются в отображаемой
или символьной форме, при этом каждый символ отображается однобайтным
двоичным кодом, а обрабатываются - во внутренней или машинной форме.
Символьная форма практически однозначно соответствует кодам
применяемым на внешних устройствах для ввода символьной информации и ее
отображения. Для преобразования данных из одной формы в другую строятся
достаточно сложные программы.
Концепция памяти в Ассемблере заключается в том, что коды и
исходные данные программы размещаются в свободных сегментах основной памяти
и могут использовать данные, определенные в предварительно подготовленных
сегментах. Задачу размещения программы поочередно решают компиляторы,
компоновщики и загрузчики программ. Корректность размещения программы
и эффективность использования свободной памяти обеспечивается
программами ОС в соответствии со стандартами распределения, свойственными каждой
модели ЭВМ.
3.2. Блоковая структура программы и ее данных
Сегменты следует рассматривать как блоки или части модулей,
изолированные с позиций прагматики проектирования программ. В современных
системах программирования и операционных системах для повышения
надежности программных модулей и их частей классифицируют ряд типов сегментов,
различающихся по периоду формирования, способу хранения и эксплуатации.
По периоду формирования и способу обработки будем различать сегменты
шести типов:
1) динамически выделяемые, существующие только часть периода
решения задачи (они чаще всего предназначены для буферных областей памяти, в
которых объем выделения зависит от наличных ресурсов ВК);
2) оперативно изменяемые, данные которых постоянно изменяются в
результате информационного обмена и преобразований машинными командами
(сюда относят сегменты стека и статически выделяемые на этапе
проектирования программ сегменты данных);
Язык Ассемблера в программировании информационных и управляющих систем 55
3) настраиваемые при внутренних переключениях режимов программы
(сюда относят сегменты, содержащие оперативные параметры управления);
4) настраиваемые на подготовительном этапе эксплуатации программы
при инсталляции для определения эксплуатационных режимов программы в
соответствии с наличными ресурсами ВК и условиями их эксплуатации (сюда
относят сегменты инсталляционных данных);
5) сегменты констант и управляющих кодов встроенных виртуальных
машин;
6) сегменты кодов базового процессора, содержащие оперативные коды
управления или двоичные тексты программ целевого процессора;
Для получения необходимых кодов программы и данных из памяти
программист должен согласовать условное содержание сегментных регистров CS,
DS и других с фактическим, сформированным вызывающими программами и
подготовленными программистом в начале выполнения программы.
Транслятор с Ассемблера, как и другие компиляторы, формирует объектные коды,
включающие указатели адресов, определяемые при компоновке объектных
модулей в единый исполняемый модуль и размещении исполняемых модулей в
памяти. Разнообразие сегментов позволяет создавать программы для размещения
программ в областях ГП с разными физическими свойствами: постоянной,
перепрограммируемой и оперативной. В постоянной или перепрограммируемой
памяти могут содержаться сегменты типов 5 и 6. Сегменты группы 4
предпочтительней хранить в перепрограммируемой памяти, а для сегментов групп 2 и 3
необходимо использовать статически выделяемую оперативную память. Для
первой группы предпочтительней использовать оперативную память,
динамически выделенную в процессе выполнения программы.
Структура сегментов, выбранная на этапе проектирования системных
программ и программ пользователя, сопровождается и поддерживается
средствами всех операционных систем вплоть до момента выполнения программ.
Загрузчики могут выполнять статическое и динамическое размещение сегментов
в памяти.
Ключевым моментом для правильного объединения пользовательских
объектных модулей с библиотечными модулями и подпрограммами языков
программирования является знание правил работы компоновщика LINK при
формировании загрузочных модулей и объединенных сегментов, а также
правил оформления сегментов директивами SEGMENT, ENDS и GROUP. При
сегментации программы все ее части, которые могут использоваться как
отдельные фрагменты, целесообразно выделить как отдельные сегментные блоки.
Сегменты, предназначенные для кодов команд, данных и стека, определяются в
соответствии с синтаксисом:
Имя сегмента SEGMENT
[Выравнивание] [Комбинирование] [Класс]
Директивы определения памяти \ Машинные команды
Имя сегмента
ENDS
56 Глава 3 Техника модульного программирования на языке Ассемблера
Операнды директивы SEGMENT имеют произвольный порядок, причем
выравнивание определяет размещение начального адреса сегмента при его
подключении в конец другого сегмента. Наиболее распространенные значения
WORD и PARA предопределяют размещение начала сегмента с адреса
кратного длине слова (2 байта) и длине параграфа (16 байтов). Комбинирование
определяет способ объединения сегментов:
• PUBLIC - делает доступными для объединения сегменты из разных
модулей;
• STACK - по возможности обеспечивает объединение всех стековых
сегментов в единый физический сегмент;
• MEMORY - обеспечивает формальное наложение друг на друга всех
сегментов с таким типом объединения, что дает возможность
использования этого сегмента как некоторой общей области;
• PRIVATE - используется по умолчанию и определяет сегмент, не
подлежащий объединению с другими сегментами.
Разные классы определяемые пользовательскими строками символов
обеспечивает разделение одноименных сегментов.
Для резервирования памяти под простые переменные и массивы
переменных используются команды резервирования памяти и определения
констант соответственно размеру с использованием операции количество DUP
значение . Примеры сегментов данных возьмем из области построения
лексических анализаторов исходных текстов символьных языков.
Буферы исходных данных
Буферы исходных данных
Буферы исходных данных
Буферы исходных данных
Конец сегмента обмена
Начало сегмента образов
Сегментный адрес образов
CHG_DATA_SEG SEGMENT
BUF_INPJ3ATA DW 256 DUP (?)
DW 256 DUP (?)
BUFJDUTJDATA DW 256 DUP (?)
DW 256 DUP (?)
CHG_DATA_SEG ENDS
IMAGE_SEG SEGMENT
ADDR_IMM_SEG DW SEG NEXT_SEG
DW 8192 DUP (?) ; Резервирование массива указателей
DB 49150 DUP (?) ; Резервирование для кодов образов
IMAGE__SEG ENDS ; Конец сегмента образов
TBJTRNJSEG SEGMENT
ТВ DB'O1 DUP (255),0,1,2,3,4,5,б,7f8,9 ; Новые коды цифр
DB 'А'-'Э1-! DUP (255),10,11,12,13,14,15
DB 'a^'F'-l DUP (255),10,11,12,13,14,15
TB_TRN_SEG ENDS ; Конец сегмента констант таблицы
; перекодирования
Среди приведенных сегментов первые два должны изменять свое
содержание при выполнении программы, а последний должен сохранять свое
значение, как и сегменты реентрантных кодов. При необходимости объединения
сегментов кодов и констант, а также логических сегментов в постоянной памяти,
Язык Ассемблера в программировании информационных и управляющих систем 57
определенных в разных модулях, целесообразно использовать директиву
GROUP в соответствии с синтаксисом:
Имя физического сегмента GROUP Список имен логических сегментов
Другой способ объединения сегментов реализован с использованием
необязательных параметров директивы SEGMENT: тип выравнивания, тип
связывания, дополнительное имя. Полученный в результате объединения
физический сегмент может иметь размер до 64 Кбайтов для реального режима.
Тип связывания в директиве начала сегмента указывает на способ
объединения данного сегмента с другими при компоновке и загрузке по
абсолютному адресу. По умолчанию сегмент является локальным. Тип связывания
PUBLIC указывает на возможность объединения данного сегмента с другими
одноименными сегментами, если они встретятся при компоновке:
• сегменты типа COMMON и MEMORY используют общую память со
всеми одноименными сегментами в других модулях, то есть разные
метки и имена переменных из разных модулей могут соответствовать одним
и тем же адресам;
• если тип связывания определяется в форме AT выражение, то
сегментный адрес определяется выражением, как правило, абсолютным;
• сегменты с типом связывания STACK последовательно размещаются в
памяти и образуют единый физический сегмент стека.
Сегменты с одинаковыми произвольными дополнительными именами,
заключенными в апострофы, компонуются в единый физический сегмент в
соответствии с типом выравнивания, устанавливающими границу начала
размещения логических сегментов в физическом. По умолчанию принимается тип
выравнивания PARA, когда сегментный адрес, с которого начинается сегмент,
кратен 16; тип PAGE - определяет адрес кратный 256, тип WORD - четный
адрес, тип INPAGE ограничивает размер сегмента 256 байтами, а тип BYTE
допускает произвольное смещение начала логического сегмента.
Программы на Ассемблере рекомендуют строить так, чтобы сегменты
данных и констант определялись перед сегментами кодов, что облегчает
транслятору определение адресов данных. В предыдущем разделе при определении
операндов машинных команд мы в основном пользовались формальным ме-
таопределением. Однако в большинстве практических случаев составления
программ важно различать, по крайней мере, два способа задания типов
данных и адресов памяти: в явном или неявном виде.
Явная форма задания операндов возлагает определение типа данного,
сегмента и смещения, а также их запись в форме определенной синтаксической
конструкции на программиста. Тип переменной, метки или абсолютного
выражения, определяющего внутрисегментный адрес, может корректироваться
операцией замены атрибута типа - тип PTR в соединении с ключевыми словами
типов: BYTE, WORD, DWORD, QWORD и TBYTE. Явное определение
сегмента задается именем сегментного регистра CS, DS, SS, ES, GS или FS с
последующим двоеточием. При этом контроль за правильностью типов операндов,
58 Глава 3 Техника модульного программирования на языке Ассемблера
соответствием адресов и сегментных регистров полностью возлагается на
программистов.
При использовании принципа умолчания типы операндов машинных
команд определяются по месту их описания. Для полного определения
относительных адресов меток и переменных на стадии компиляции необходима
информация о возможности обращений к данным через сегментные регистры,
которая определяется директивой ASSUME с таким синтаксисом:
ASSUME список назначений;
где назначение: := сегментный регистр : имя сегмента
Назначение регистра CS, кроме того, дает возможность определять
метки в текущем сегменте. Загрузчик исполняемых модулей MS DOS после
размещения программы в памяти готовит в SS:SP начальный адрес стека задачи, в
DS - сегментный адрес блока PSP, и передает управление по адресу,
определенному в последнем операторе модуля:
END стартовая метка программы
Поэтому перед работой с данными программы по относительным
адресам нужно подготовить возможность выхода из программы в ОС и разместить
соответствующие сегментные адреса в регистрах DS и ES.
Схематично программа для MS DOS может быть представлена так:
STK SEGMENT STACK
DW 100 DUP (?) ; Резервирование памяти для стека
STK ENDS
CGROUP GROUP CQDESEG,TB_TRN_SEG ; Определение
; объединенного сегмента
DGROUP GROUP OP_DATAJ3EG,CHG_DATA_SEG
CODESEG SEGMENT
ASSUNE CS:CGROUP,DS:DGROUP,ES:IMAGE_SEG
START:PUSH DS ;Сохранение сегментного адреса PSP
XOR AX,AX ; Очистка АХ для формирования смещения PSP
PUSH AX ; Сохранение смещения
MOV AX,DGROUP ; Формирование сегментного адреса
MOV DS,AX ; Размещение адреса сегмента данных
MOV AX,SEG IMAGE_SEG ; Формирование адреса сегмента
MOV ES,AX ; Размещение адреса дополнительного
; сегмента
команда программы для решения задачи
команды для возврата в ОС
CODESEG ENDS
END START
В общем случае существует типовая тенденция изображать обращение к
задаче как стандартный вызов подпрограммы.
Язык Ассемблера в программировании информационных и управляющих систем 59
3.3. Модульное программирование с использованием
языка Ассемблера
Модульное программирование создавалось как мощное средство для
сокращения затрат на разработку программ, прежде всего, за счет создания таких
функциональных модулей широкого применения как стандартные процедуры и
функции. Использование такого подхода оказалось полезным еще по двум
направлениям проектирования программ:
• распределение работы по созданию большого проекта на нескольких
программистов;
• возможность предварительной автономной отладки и испытаний
отдельных модулей на этапе их проектирования и проверки.
Главным исполнительным механизмом модульного программирования
исторически является программа-компоновщик LINK, которую в ранних
версиях называли редактор, а точнее, "сокращатель" связей (linkage editor), в то
время как сейчас прямым и точным переводом английского термина "linker"
является "связыватель", который заменен более мягким термином
"компоновщик". Главная задача этой программы - скомпоновать комплекс объектных
модулей, полученных от компиляторов с различных языков в какую-нибудь из
разновидностей исполняемых модулей и заменить неопределенные ссылки на
внешние имена соответствующими адресами или информацией доступной для
загрузчиков.
Классификация типов связей разграничивает два класса: управляющие и
информационные, для реализации которых используется единый технический
подход, опирающийся на глобально доступные имена элементов программных
модулей. Такие имена специфицируются практически во всех современных
языках ключевым словом public и представляют точки входа (этому названию
соответствует и устаревшее ключевое слово entry) или первые исполняемые
команды подпрограммы. Фактически это имена ячеек внутри модуля, на которые
можно ссылаться из других модулей, в которых эти же имена определены
ключевым словом external или родственными ключевыми словами.
Управляющие связи в современных системах программирования
определяются способами передачи управления и действиями, выполняемыми для
решения частей общей задачи и представленные в одной из шести форм:
• вызов подпрограммы того же загрузочного модуля в процессе его
выполнения, реализуемой простыми средствами модульного
программирования;
• макровызов как подготовка действия по решению части задачи на этапе
компиляции или создания программы, который реализуется с помощью
встроенных макросредств системы программирования;
• вызов подпрограммы, копия которой имеется в главной памяти,
который обычно реализуется в форме обращения к общей системной
программе операционной системы или управляющей надстройки
пользователя;
60 Глава 3 Техника модульного программирования на языке Ассемблера
• вызов подпрограмм с динамической подгрузкой их кодов и
управляющих данных, при котором иногда различают оверлейную структуру
программ с фиксированным взаимным расположением модулей и
фрагментов, и динамическую подгрузку в совершенно произвольном порядке;
• динамический вызов параллельных (а при отсутствии дополнительных
процессоров псевдопараллельных) процессов решения подзадач,
включая выполняемые ЕХЕ-модули многих ОС и dll-модули для систем типа
MS Windows, подключающие модули на этапе выполнения;
• передача сообщений и сигналов автономным параллельным процессам
и задачам, широко применяемая в настоящее время для управления
вычислениями в объектно-ориентированных оболочках типа Windows.
Кроме управляющих необходимо установить и информационные связи
между модулями для передачи в подчиненные модули оперативных данных или
аргументов подпрограмм, и возврата результатов в вызывающие программы, а
также для передачи универсальных управляющих данных в другие
программные модули. К основным современным способам установления
информационных связей относят:
• передачу аргументов при вызове подпрограмм и функций,
унифицируемую стандартами обращения к подпрограммам, общими для
компоновщиков и специализированными для языков программирования;
• возврат результатов при вызове функций, при котором соблюдают
стандарт, связанный с языком программирования;
• использование глобальных областей памяти для обмена данными между
подпрограммами и задачами.
К устаревшим к настоящему времени, но по-прежнему используемым
способам установления информационных связей следует отнести:
• общие области COMMON языка Фортран, которые доступны из разных
процедур, функций и программ;
• установление эквивалентности последовательных областей памяти в том
же Фортране с помощью операторов EQUIVALENCE, которые
потеряли свою актуальность в связи со сложностью контроля корректности
таких соответствий. В языке высокого уровня подобные действия
относительно легко можно выполнить переприсваиванием указателей разных
типов.
Комбинированные связи чаще всего возникают тогда, когда
управляющая или адресная информация рассматривается как данные в аргументах
функций и процедур и таблиц имен или адресов подпрограмм. Но с другой
стороны точки ветвления могут быть рассмотрены как адреса передачи
управления. Для их реализации достаточно использовать типичные средства
связывания с незначительными особенностями во входных языках для определения
процедурных типов и др.
Для реализации компоновщика LINK и умелой его эксплуатации
необходимо проанализировать форматы его элементов хотя бы на самом общем
Язык Ассемблера в программировании информационных и управляющих систем 61
уровне. При изучении форматов объектных модулей особый интерес представляют
такие вопросы:
1. Как определяется, какие внешние подпрограммы и данные нужны
объектному модулю, и каким образом определяются адреса обращения или
"точки входа" в подпрограммах или данных, которые могут использоваться
программами других модулей?
2. Как проверить корректность типов данных при обращении к
подпрограммам и задачам?
3. Как достигается переместим ость объектной программы, т.е.
возможность размещать ее в любом месте оперативной памяти?
4. Как и почему компонуются отдельные логические сегменты в единый
физический сегмент?
Объектный модуль состоит в основном из двоичных текстов - команд на
машинном языке и данных, которые составляют объектную программу и
переносятся в память при загрузке программы без изменений. Модуль может
содержать один или больше таких сегментов машинного текста, называемых
кодовыми сегментами. Внутри сегмента кодов, как и в других сегментах, позиция
каждого байта текста относительно начала сегмента фиксирована, и этот
порядок должен быть сохранен при загрузке сегментов в память перед выполнением
программ. Порядок размещения сегментов в памяти может быть
произвольным, и сегмент можно поместить в любое место памяти.
Таким образом, относительный адрес любой входной точки
относительно начала сегменту есть константа и для определения такого имени для
компоновщика достаточно рядом с его символичным образом определить сегмент и
относительный адрес. Словарь внешних символов ESD (external symbol
dictionary) определяет и идентифицирует (дает единое имя и номер) таким
объектам: программным сегментам объектной программы; внешним ссылкам на
программные сегменты и другие входные точки, которые используются или к
которым обращаются из этого модуля, но которые не включены в него. Эти
связи определяются для каждого модуля компоновщиком LINK.
Объединение логических сегментов в большие физические сегменты
требует определения правил коррекции относительного адреса в предварительно
сформированном адресе команды путем добавления относительного смещения
начала логического сегмента для переместимых адресов. В случае
использования внешних имен условный код указателя конкретным адресом
соответствующего объекта. Многообразие объектов переместимости, а иногда и
использование разных кодов для внутреннего представления типов переместимости
делают несовместимыми объектные коды, получаемые от трансляторов разных
фирм или даже разных систем программирования одной фирмы. Однако
имеющаяся тенденция унификации таких кодов постепенно сглаживает имеющиеся
противоречия.
В модульном программировании различают связи по управлению и по
данным, реализуемые с помощью адресных указателей или ссылок на данные и
команды. При объединении модулей необходимо определить имена областей
памяти и фрагментов программ, используемые в других модулях, в списке one-
Глава 3 Техника модульного программирования на языке Ассемблера
рандов оператора PUBLIC. Внешние имена из других модулей вместе с их
типами описываются в списке операндов оператора EXTRN в форме имяжип.
Для указателей используются только ключевые слова WORD и DWORD, то
есть указатели в ассемблере не типизированы и ответственность за
правильность типов указываемых конструкций ложится на программиста. При
обращении к подпрограммам и функциям по прямым адресам используются типы
NEAR и FAR. Передача параметров в форме непосредственных значений или
адресных указателей осуществляется через стек.
Необходимость проверки соответствия типов данных, аргументов,
процедур и функций, а в объектно-ориентированных системах и числа аргументов
в процедурах заставила существенно усложнить представления словаря
внешних ссылок в объектных файлах современных систем программирования.
Современные компоновщики используют программные модули, хранящие
информацию об именах исходных модулей программы, типе компилятора,
породившем этот модуль, и иногда даже и о модели памяти, под которую этот модуль
оттранслирован. Использование сегментной структуры программ может также
облегчить распределение сегментов по таким разным видам памяти как ОЗУ.
ПЗУ и ППЗУ.
3.4. Базовые директивы для оформления модулей
в языке Ассемблера
Разбиение или декомпозиция задачи на отдельные программные
модули, которые могут быть составлены на разных языках программирования,
упрощает ее отладку, тестирование, документирование. В Ассемблере
модульность обеспечивается такими методами:
« использование процедур и функций для создания связей по управлению;
• использование директив языка Ассемблер для организации связей;
• комбинирование и объединение сегментов с одновременным
структурированием данных таким образом, чтобы к ним можно было обращаться
из разных модулей (организация структур, записей, сегментов и методы
их объединения, редактирование связей по управлению и данным);
• использование макросредств и структурных блоков.
Сложная программа, как правило, разделяется на фрагменты -
программные модули из функциональных прагматических соображений. При этом
разные модули могут разрабатываться автономно, более упорядоченным
образом выполняется отладка и тестирование, модификации в программе
оказываются локализованными. Часто решаемые задачи желательно оформлять как
модули, которые хранятся в библиотеках и используются несколькими
программами. Для реализации модульности программ используются
макросредства, процедуры или подпрограммы, а также структурирование данных для
использования в разных модулях.
При модульном программировании необходимо организовать как связи
по управлению, так и по данным, которые создаются с использованием непо-
Язык Ассемблера в программировании информационных и управляющих систем 63
средственных адресов в командах в форме указателей на данные и команды.
Данные, с которыми работает процедура или ее аргументы, могут передоваться
через регистры или память. Использование регистров для этой цели
оказывается неудобным для управления системой из-за существенного разнообразия
типов регистров. Это затрудняет передачу параметров при использовании
агрегатов данных и данных математического сопроцессора. Для передачи
параметров процедуре через память список аргументов размещается в некоторой
подключаемой области данных, начальный адрес которой передается процедуре.
Чаще всего параметры процедуре передаются через стек, в который
промежуточные данные помещаются и удаляются по мере необходимости.
Процедуры со стековой организацией передачи аргументов могут легко
вызывать другие процедуры, то есть допустимы вложенные вызовы процедур.
Число уровней вложения ограничено только размером сегмента стека (в
реальном режиме процессора ix86 - 64 Кбайт), для большей глубины вложенных
вызовов необходимы переключения стековых сегментов. В защищенном режиме
вместо этой проблемы возникает проблема правильной оценки необходимой
глубины стека, что особенно важно при использовании рекурсивных вызовов.
Процедуры, определяемые как интегрированные действия или
операционные ресурсы, являются основным средством деления программ на отдельные
функциональные модули. Процедурой называется исполняемый код, к
которому можно обратиться с последующим возвратом к следующей команде. В
базовом Ассемблере процедуры оформляются как блоки и представляют собой
последовательность инструкций и директив, образующих некоторую
подпрограмму в пределах одного сегмента в виде:
имя процедуры PROC [тип]
инструкции и директивы или тело процедуры
RET ; Инструкция выхода из процедуры
имя процедуры ENDP
Директивы PROC и ENDP определяют соответственно начало и конец
процедуры и должны начинаться одним и тем же именем. Необязательный
параметр тип может быть определен как FAR или NEAR по умолчанию, им
определяется тип всех команд RET в блоке. Переход к процедуре называется
вызовом и выполняется командой CALL. Имя процедуры имеет атрибут метки и
может использоваться как операнд в инструкциях переходов, вызовах или
циклах. Организация вложенных вызовов процедур с точки зрения
программирования упрощается благодаря использованию стека, но требуется контроль
переполнения стека во время выполнения программ. В современных моделях
процессоров эта функция возложена на систему прерываний в форме обработки
особых ситуаций.
Использование процедур при программировании обеспечивает
эффективное использование памяти и других ресурсов ВК во время разработки,
трансляции, отладки и эксплуатации программ. Процедуры в основном
разрабатываются как функциональные и процедурные расширения базового языка
программирования. Более существенными являются преимущества языковых и
D^f Глава 3 Техника модульного программирования на языке Ассемблера
объектно-ориентированных расширений, однако их недостаток в Ассемблере
может быть частично скомпенсирован макросредствами.
Язык Ассемблера в принципе не накладывает никаких ограничений на
передачу аргументов, результатов и различия между ними при выполнении
процедуры. Если вызывающая программа или процедура использует одни и те
же регистры, то они могут использоваться только для временного хранения
данных, или при вызове процедуры их содержимое необходимо запоминать, а
при возврате в вызывающую программу восстанавливать. Чаще всего для
этого используется стек. Наиболее эффективным способом с точки зрения
скорости выполнения при передаче ограниченного количества аргументов является
использование регистров. А исходя из требований гибкости наиболее
компактным по объему спецификаций разнообразных аргументов является метод их
передачи через стековый кадр. Такой подход лежит в основе стандартных
соглашений о связях с подпрограммами в разных языках программирования,
которые будут рассмотрены ниже.
Для объединения модулей необходимо определить метки и переменные,
используемые в программах или процедурах других модулей в списке
операндов директив PUBLIC. Имена, определенные таким образом, становятся
глобальными, но могут быть использованы при программировании нового модуля
только при условии, что они и их типы определены в списке операндов
директив EXTRN нового модуля программирования. Каждое имя может быть
определено директивой PUBLIC в программном модуле лишь однократно.
Синтаксис этих директив:
PUBLIC имя [,...]
EXTRN имя : тип [,..,]
Тип может определяться словами: BYTE, WORD, DWORD, QWORD,
TBYTE, именем структуры или записи для переменных и NEAR или FAR для
меток при обращении к процедурам и функциям по прямому адресу.
Один из объединяемых модулей является главным; с него начинается
вычисление, так как он содержит точку входа в программу и заканчивается директивой:
END точка входа
Все другие модули заканчиваются просто безоперандным END.
Отдельные программные модули транслируются соответствующими компиляторами,
а затем компонуются для MS DOS программой LINK в один ЕХЕ-
выполняемый файл или при ограничении размеров сегмента 64 Кбайтами в
один СОМ-файл при использовании компоновщика TLINK. Соответвующие
программы имеются и для MS Windows, OS/2 и других ОС.
Предыдущие директивы имеют явные аналоги в языках
программирования высокого уровня или реализуются по умолчанию как например,
глобальные имена в языке С. Кроме них в Ассемблере процессоров ix86 используется
дополнительная категория - сегменты памяти, которые создаются также и в
Язык Ассемблера в программировании информационных и управляющих систем 65
объектных модулях, прокомпилированных с языков высокого уровня с
именами, определенными стандартами языков и фирм разработчиков.
5.5. Общие принципы организации межмодульных
связей в языках высокого уровня
Определение внутренней структуры объектного модуля является
основополагающим моментом реализации системы модульного программирования и
для процессоров ix86/87 опирается на сегментную структуру модулей.
Объединение логических сегментов данных в большие физические сегменты в
компоновщиках опирается в первую очередь на объединение фрагментов
одноименных и однотипных сегментов, определенных в разных модулях и возможность
объединения отдельных сегментов в группы.
Для правильного обращения к подпрограммам существенную роль
играет выбранная модель памяти, определяющая вместе с наличием
математического сопроцессора тип используемой библиотеки. Наиболее распространены
следующие модели памяти:
tiny - с единственным физическим сегментом для кодов программ и данных;
small - с одним сегментом для кодов и одним сегментом для данных;
medium - с одним сегментом для данных и несколькими - для кодов;
compact - с одним сегментом для кодов и несколькими - для данных;
large и huge - с несколькими сегментами кодов и данных.
Отличие памяти типа huge состоит в том, что здесь разрешены блоки
данных объемом превышающие физический сегмент, в том числе и для
сегмента глобальных данных. В защищенных режимах особой популярностью
пользуется так называемая плоская или простая модель памяти (flat), в соответствии с
которой все статически определенные логические сегменты размещаются в
едином, но достаточно большом физическом сегменте памяти, однако при ее
использовании теряется возможность использования внутренней защиты для
устранения влияния ошибок во внутренних модулях программы.
Другая важная особенность технологии модульного программирования
определяется правилами построения внешних имен определенных в языках
высокого уровня. В основном эти особенности связаны с использованием
специальных знаков подчеркивания, а также с правилами использования заглавных
и строчных букв в именах компоновщиков.
Для использования библиотечных функций ввода-вывода языка
Ассемблера, как и других языков, в состав загрузочного модуля должны быть
включены базовые программы, выполняющие подготовительные и
заключительные операции, обработку особых ситуаций и реализующие среду языка.
Обычно имена сегментов определяют компиляторы в соответствии с
определением модели памяти и стандартом, или правилами фирмы-разработчика,
применяемыми для образования имени объектного файла. Обычно в сегменте
кодов объединяют команды главной программы, команды процедур и
функций. Кроме того, в него можно включить и константы программы, но их чаще
сохраняют в отдельном сегменте.
3 В.И. Пустоваров
66 Глава 3 Техника модульного программирования на языке Ассемблера
В состав объектного модуля включают и сегмент определения
глобальных данных главной программы или подпрограмм. Однако нет необходимости
строго придерживаться этих категорий; сегмент с любым машинным текстом
можно, если это удобно, включить в сегмент кодов.
Реализации процедурных и функциональных языков высокого уровня и
их расширений традиционно строятся через библиотеки стандартных процедур
и функций, включающие их в форме отдельных атомарных модулей,
подключаемых компоновщиком.
Основу модулей составляют процедурные блоки, которые для придания
логической стройности процессу программирования должны выполнять
логически законченные функции или задачи, и блоки информационных или
управляющих констант, или статически зарезервированной памяти. Ответственность
за передачу параметров и их использование в процедуре возлагаются на
программиста. В соответствии со стандартными соглашениями о связях,
принятыми в языках высокого уровня для гармоничной и компактной спецификации
информационных связей, параметры размещаются в стеке, вершина которого
определяется содержимым регистров SS и SP. В некоторых специальных
случаях параметры передают через регистры ЦП или сопроцессора. Для передачи
параметров вызывающая последовательность должна разместить параметры в
стеке или регистрах, а сама процедура - использовать эти данные. Остановимся
подробнее на соглашениях по передаче параметров, характерные для языков С
и Pascal.
5.5.7. Типы данных языков высокого уровня
и особенности их реализации
Типы данных основных языков программирования и их численные
характеристики в реализациях, использующих идеологию фирмы Microsoft для
16-битового режима, сведены в таблице 3.1. По ней можно сделать выводы о
совместимости типов в разных языках. Важным моментом является реализация
базовых форматов данных, какими для процессоров ix86 являются данные
целочисленного формата базового для процессора. Для языка С такими
базовыми типами являются int - целое со знаком и unsigned - целое без знака,
определяемого 16- или 32-битовым вариантом защищенного режима. Поэтому
системы программирования, ориентированные на 32-битовый режим имеют
соответствующую длину базовых форматов. В этом случае сокращение длины вдвое
задается ключевым словом short; а увеличение длины вдвое - long.
Важной особенностью языка С в отличие от языков типа Pascal является
возможность задания управляемого числа параметров в функциональном
вызове. Следствием этого должна быть последовательность размещения
аргументов с легко определяемой позицией первого аргумента в стеке. Поэтому в
процессорах ix86 для языка С становится предпочтительней запись аргументов в
стек начиная с последнего аргумента, при которой первый аргумент
оказывается наверху стека.
Язык Ассемблера в программировании информационных и управляющих систем 67
Таблица 3.1
Встроенные арифметические типы данных языков IBM/PC
Характеристики типов
Класс
целые
знаковые
беззнаковые
Десятичные
знаковые
действительные для языка
действительные для
сопроцессора
Разряды
бв.
1
2
4
8
1
2
4
8
10
6
4
8
10
dec.
2-3
4-5
9-10
15-16
2-3
4-5
9-10
15-16
16
11-12
7-8
15-16
19-20
Диапазон
значении
-V 2^-1
-2'5 215-1
-211 231-1
-2". 2«-1
0. 28-1
0 2>Ч
0 2^2-1
0 264-1
.1016-1 10I6-]
2 9е-3 1 7еЗ
1 5е-45 3 4е38
5 Ое-324
1 7еЗО8
3 4е-4932
1 1е4932
Ассемблер
Дирек-
пшва
db
dw
dd
dq
db
dw
dd
dq
dt
-
dd
dq
dt
Tun
BYTE
WORD
DWORD
QWORD
BYTE
WORD
DWORD
QWORD
TBYTE
-
DWORD
QWORD
TBYTE
Название типа
высокого у
Pascal
Shortint
Integer
Longint
-
Byte
Word
-
-
-
Real
Single
Double
Extended
С
char
int
long
-
unsigned
char
unsigned
|int|
unsigned
long
-
-
-
float
double
long
double
в языках
ровня
Модула-2
CHAR
INTEGER
-
-
CHAR
CARDINAL
LONGCARD
-
-
-
REAL
LONGREAL
-
Примечание ДР - десятичные разряды в отображении данных.
Важным моментом является способ работы с указателями или адресами.
В языке С применяются строго типизированные указатели и при определении
имен адресных переменных в операторе спецификации задается знак "*",
предшествующий имени указателя. Для адресов реального режима существенную
роль играет его тип NEAR - для внутрисегментных указателей, занимающих 2
байта, FAR - для межсегментных указателей, занимающих 4 байта, такой же,
как и для целочисленных типов со знаком. При явном задании таких
указателей можно построить так называемые смешанные модели памяти, с помощью
которых можно обращаться к данным, определенным в нестандартных
сегментах памяти.
Данные типа char всегда занимают один байт, соответствуют данным
Ассемблера, определенным в директивах db, не содержащих строк символов и
имеют следующее представление в памяти, состоящее из знакового разряда и
числа. При передаче данных типа char в качестве параметров подпрограммы
или функции их необходимо преобразовывать к целому типу. Пример:
_charArg db 23 ;
MOV al, _charArg ; аргумент в AL
CBW ; преобразовать к слову со знаковым расширением
PUSH ax ,• поместить в стек
ОО Глава 3 Техника модульного программирования на языке Ассемблера
Для беззнаковых чисел старшие разряды должны быть очищены
командой xor ah,ah вместо cbw.
Данные типа short и int (знаковые) занимают два байта и имеют
двухбайтное представление в памяти (пример):
_intArg
_shortArg
PUSH
PUSH
dw -23 ;
dw 44 ;
_intArg
_shortArg
поместить в стек
поместить в стек
Данные типа long и float занимают четыре байта и записываются в стек
следующим образом:
__longArg dd 23
PUSH
PUSH
_ХопдАгд
JLongArg
поместить в стек старшие разряды
поместить в стек младшие разряды
А восьмибайтные данные типа double могут быть перенесены в стек либо
путем четырехкратной записи в стек, либо путем использования операций
сопроцессора, которые будут рассмотрены ниже.
3.5.2, Особенности организации связей в языке С
В языке С функция может иметь переменное число параметров, поэтому
передача параметров осуществляется в порядке, обратном указанному в
описании функции. Таким образом, первый параметр всегда находится в вершине
стека перед передачей управления функции. Если, например, функция имеет
следующее описание:
void func(int a, long b, int с) ;
Тогда перед обращением к этой функции командой CALL программа на
языке Ассемблера должна привести стек в следующее состояние:
—> направление роста адресов
а
SP
SP+2
SP+6 SP+8
Поскольку число параметров при вызовах подпрограмм в языке С не
зафиксировано жестко, то вызываемая функция не удаляет параметры из стека
при возврате в вызывающую программу. Команда удаления параметров из
стека должна быть помещена сразу после команды вызова функции.
PUSH
PUSH
PUSH
PUSH
CALL
ADD
с
b + 2
b
a
func
sp, 8
Язык Ассемблера в программировании информационных и управляющих систем 6Q
Пример. Полный фрагмент вызова описанной выше функции на языке
Ассемблера:
загрузка значения параметра С в стек
загрузка старших разрядов В в стек
загрузка младших разрядов В в стек
загрузка значения параметра А в стек
вызов функции func
удаление параметров из стека
где 8 - количество байтов, занимаемых
параметрами а, Ь, с,
Если передача параметров осуществляется с помощью адресной ссылки,
что удобно при работе с большими структурами данных, либо параметр не
может быть загружен в стек (например, массивы и их вариант - символьные
строки), перед обращением к функции в стек записываются не значения
параметров, а их адресные указатели.
Необходимо помнить, что в компиляторах Microsoft С версий 5.2 и
более ранних значения параметров типа float при обращениях к функциям
необходимо преобразовать в тип double и только затем поместить в стек.
3.5.3. Особенности организации связей в языке Pascal
Подпрограмма на Ассемблере из основной программы на языке Pascal
вызывается как внешняя процедура или функция. При вызове функции
параметры помещаются в стек в порядке их определения в вызове. Перед выходом
из функции все параметры из стека удаляются. На время выполнения
процедуры или функции необходимо сохранять содержимое регистров ВР, SP, SS, DS,
если они изменяются. Пример программы на языке Pascal, которая обращается
к ассемблерной функции
program Asmtst(input, output);
type intvect = array [1..16] of integer;
var v:intvect; x,n::integer;
function IvSrc(x:integer; n:integer; v:intvect); integer;
extern ;
begin Ввод значений п и вектора, v
writeln(T x>=?, IvSrC(x,n,v));
end.
PUBLIC IvSrc ; Функция IvSrc, вызываемая из Pascal
IvSrc PROC ; Процедура должна находиться в сегменте кодов
Сохранение старого ВР
Определение базы зоны параметров
Копирование сегментных регистров
для SMALL-модели памяти
Пересылка х в АХ
Пересылка п в СХ
push
mov
push
pop
mov
mov
bp
bp,
ds
es
ax,
ex,
sp
[bp+8]
Cbp+6]
/О Глава 3 Техника модульного программирования на языке Ассемблера
mov di, [bp-f4]
scasw
jnl $+4
loop $-3
mov ax,es:[di-2]
pop bp
Пересылка указателя v
Сравнение
Выход при успехе
Возврат на начало цикла
Вычисление степени через сдвиг
Восстановление старого ВР
ret б ; выход с извлечением 4 байтов аргументов
IvSrc ЕШ)Р ; Значение функции возвращается в АХ
Тип процедуры определяется в зависимости от используемой в языке
программирования модели памяти: для tiny, small и compact используется тип
NEAR, а для medium, large и huge - FAR.
При использовании процессоров 80186 и выше связь с языком Pascal
упрощается благодаря использованию команд ENTER и LEAVE.
IvSrc PROC
ENTER 0,3
LEAVE
RET 6
IvSrc ENDP
Модификация процедуры для процессоров 80186 и
выше
Функция не имеет локальных переменных
и находится на уровне выполнения 3.
Удаление из стека текущего стекового кадра.
Подпрограмма имеет 3 параметра слова.
Параметры могут передаваться по значению или по указателю, то есть в
стек загружается или само значение, или указатель на ячейку памяти:
• параметры-переменные (var) всегда передаются по указателям;
• параметры-значения и массивы, или записи передаются непосредственно
через стек, если их длина не превышает 4-х байтов, а иначе по указателю;
• байтовые и символьные переменные передаются как слова с
неопределенным старшим байтом;
• параметры булевского типа передаются как байт со значением 0 или 1;
• параметры типа real языка Pascal передаются как 6 байтов;
• параметр типа указатель передаются в виде двойного слова,
содержащего OFFSET и SEG соответственно в младших и старших байтах. Таким
образом, последовательность ассемблерных команд для вызова функции
IvSrc будет такой:
Запись в стек 1-го параметра
Запись в стек 2-го параметра
Запись в стек 3-го параметра
Обращение к функции
Результат вычисления функции возвращается в зависимости от типа
через соответствующие регистры: АХ - целые, символьные, булевские,
перечислимого типа, а также слова; AL - байты; DX, АХ - двойные слова; DX, ВХ, АХ -
PUSH
PUSH
MOV
PUSH
CALL
X
n
AX,OFFSET V
AX
IvSrc
Язык Ассемблера в программировании информационных и управляющих систем 71
данные действительного типа; регистр сопроцессора st(O) - значения,
используемые в МСП; DX (SEG), AX (OFFSET) - данные типа указатель.
В языке С передача параметров происходит в последовательности
обратной их определению в функции, таким образом первый параметр всегда
находится в вершине стека на время передачи управления функции. В
зависимости от модели памяти указатели в С могут быть двухбайтные при типе near, или
четырехбайтные при типе FAR. Данные типов float и double передают через
четырех- и восьмибайтные области стека. Результаты вычисления функций типов
float и double возвращаются в библиотеках фирмы Borland (включая и Turbo-
системы) через верхний регистр стека сопроцессора st(O), а в библиотеках
Microsoft, включая Quick-системы, возвращаются указатели на результат
вычисления функций, который находится в области данных. Так, например, для
tiny, small и medium моделей памяти в регистре АХ возвращается
внутрисегментный, относительно DS, указатель результата, а если нужен полный
указатель, то адрес сегмента дополнительно возвращается в DX. Более подробно с
конкретными вариантами возвращения результатов можно ознакомиться в
фирменных инструкциях по использованию библиотечных программ.
В языке С предусмотрен форматный вывод на устройство отображения
или в стандартный файл вывода stdout с помощью стандартной функции printf,
в форме:
printf (строка формата, список аргументов вывода)
Строка формата определяется или в виде строковой константы,
включающей символы кода ASCII (КОИ-8), или указатель на массив символов.
Синтаксис оператора ввода scanf похож на синтаксис printf, но форматная строка
может включать только символы форматов и специальные форматы
спецификаций символьного ввода по образцу. Эта функция обеспечивает ввод данных
из стандартного файла ввода stdin. Количество аргументов определяется
количеством спецификаторов преобразования данных.
В строке формата могут определяться специальные символы с помощью
знака "\" и спецификаторы преобразования, начинающиеся знаком "%". Для
вывода одного символа из строки формата "%" его необходимо повторить в
строке.
Спецификатор преобразования в общем случае имеет синтаксис:
%[выравнивание] [ширина][префикс формата] спецификатор формата
При выполнении функции printf строка формата передается в выходной
файл без изменения до получения первого символа %. После этого очередной
операнд преобразуется в соответствии с очередным спецификатором, результат
этого преобразования также попадает в выходной файл. Признаком конца
строки во внутренней форме является байт с нулевым значением. Вызывающие
последовательности для обращения к этим подпрограммам должны включать
запись в стек значений всех численных аргументов и указателей для строк.
Заканчивается вызывающая последовательность записью в стек внутрисегмент-
72 Глава 3 Техника модульного программирования на языке Ассемблера
ного адреса строки формата вместе с командой call такого типа, который
соответствует данной модели памяти.
Таблица 3.2
Сегменты для стандартных моделей памяти Microsoft С
Модель
Small
Medium
Compact
Compact
Lage или
Huge
Имя
сегмента
_TEXT
_DATA
CONST
_BSS
STACK
name_TEXT
DATA
CONST
_BSS
STACK
_TEXT
FAR_DATA
FAR BSS
DATA
CONST
BSS
STACK
name_TEXT
FAR DATA
FAR_BSS
DATA
CONST
_BSS
STACK
Вырав-
нивае-
ние
WORD
WORD
WORD
WORD
PARA
WORD
WORD
WORD
WORD
PARA
WORD
WORD
WORD
WORD
WORD
WORD
PARA
WORD
WORD
WORD
WORD
WORD
WORD
PARA
нирование
PUBLIC
PUBLIC
PUBLIC
PUBLIC
STACK
PUBLIC
PUBLIC
PUBLIC
PUBLIC
STACK
PUBLIC
PRIVATE
PRIVATE
PUBLIC
PUBLIC
PUBLIC
STACK
PUBLIC
PRIVATE
PRIVATE
PUBLIC
PUBLIC
PUBLIC
STACK
Класс
ii V
CODE
DATA
CONST
BSS
STACK
CODE
DATA
CONST
BSS
STACK
CODE
FAR_DATA
FAR BSS
DATA
CONST
BSS
STACK
CODE
FAR DATA
FAR BSS
DATA
CONST
BSS
STACK
Группа
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
DGROUP
Содержимое
сегмента
Выполняемые
коды
Инициированные
данные (ИД)
Константы
Неинициируемые
данные (НИД)
Область стека
Выполняемые
коды
ид
Константы
нид
Область стека
Выполняемые
коды
Длинные ИД
Длинные НИД
ИД
Константы
нид
Область стека
Выполняемые
коды
Длинные ИД
Длинные НИД
ид
Константы
нид
Область стека
Поскольку количество параметров при вызовах подпрограмм в языке С
не зафиксировано, то в процессе возврата функция не удаляет параметры из
стека. Команда удаления параметров из стека размещается непосредственно
после команды вызова функции в форме:
IRP ±,<Список указателей или данных>
PUSH i ; Запись очередного двухбайтного аргумента
ENDM ; Конец блока повторений
Язык Ассемблера в программировании информационных и управляющих систем 7 3
CALL __printf ; Обращение к функции отображения
ADD SP, Общее количество байтов в стеке
Для форматного ввода данных в языке С используется стандартная
функция scanf (строка формата, список аргументов ввода), в которой
используется упрощенная строка формата, а аргументы определяются указателями
простых данных. Так, например, для вывода одного двойного слова могут
быть использованы такие директивы и команды:
form DB '%g',0 ; Поле формата в сегменте констант,
dwrd DD ? ; Поле приемника данных в сегменте данных.
MOV AX,offset DGROUP:dwrd
PUSH AX ; Запись указателя поля приемника данных.
MOV AX,offset DGROUP:form
PUSH AX ; Запись указателя формата в стек.
CALL _scanf ; Обращение к функции ввода (сегмент кодов).
ADD SP,4 ; Компенсация записей в стек.
При работе с модулями, составленными на разных языках
программирования важно придерживаться правил объединения сегментов, определяемых
компоновщиком при формировании исполняемых модулей pi объединенных
сегментов, а также правил оформления сегментов директивами SEGMENT,
ENDS и GROUP. В предыдущем примере важным является появление имени
логического сегмента с двоеточием (DGROUP:) после слова OFFSET. Это дает
транслятору с Ассемблера, а затем и компоновщику информацию о
необходимости вычисления смещения в команде относительно группового физического
сегмента, адрес которого будет находиться при выполнении программы в
регистре DS, а не логического сегмента данных программного модуля, где он
фактически определяется.
Если сегмент, в котором определено имя, имеет тип PUBLIC,
COMMON, STACK, то директива EXTRN располагается после директивы
SEGMENT, иначе, или когда сегмент неизвестен, директива EXTRN
определяется вне сегментного блока SEGMENT ENDS, в начале модуля. Если при
программировании на языке Ассемблера используются системные модули обмена
для действительных чисел из библиотеки какого-то языка программирования,
перед их вызовом необходимо правильно установить среду соответствующего
языка с помощью служебных функций. При работе с Microsoft и Quick С для
этого целесообразно использовать стандартную входную точку библиотеки
_astart и средства работы с действительными числами fltused.
Например, при использовании SMALL модели памяти схематически
модуль должен выглядеть так
EXTRN astart:near
DGROUP GROUP _DATA,STACK
DGROUP GROUP _DATA,STACK
ASSUME CS: TEXT,DS:DGROUP
74 Глава 3 Техника модульного программирования на языке Ассемблера
_DATA SEGMENT PUBLIC 'DATA1
_DATA ENDS
JTEXT SEGMENT PUBLIC 'CODE1
start: jmp __astart ; Переход на инициализацию среды
_jnain proo near
jnaain endp
JTEXT ENDS
STACK SEGMENT STACK 'STACK'
STACK ENDS
END start
Еще одним важным моментом является передача параметров в
программу из ОС. Так инициаторы систем Borland и Microsoft C/C++ передают
аргументы программы, разделенные пробелами, в форме массива строк символов с
именем argv и размерностью argc в соответствии с прототипом главной
программы:
void rnain(int argc, char *argv|])
Передача аргументов обеспечивается программами загрузки среды.
Средства модульного программирования с одной стороны представляет
мощную технологическую основу для комплексной и автономной отладки
программных модулей, а с другой стороны для эксплуатации ранее построенного
разными программистами программного обеспечения. Автономная отладка
модулей при рациональном подборе диапазонов входных данных и
результатов, а также при использовании функционально достаточных тестов и
контрольных примеров, существенно облегчает построение корректных программ.
Обычно коммерчески распространяемые программные продукты на уровне
процедурных или функциональных пакетов сопровождаются
демонстрационными контрольными примерами, позволяющими проверить возможности
программных пакетов.
Базовой программой модульной технологии является редактор
межмодульных связей или компоновщик LINK и наличие модульных режимов в
компиляторах. Разновидности модульной технологии предполагают
использование оверлейных, то есть поочередно сменяемых модулей, и динамически
загружаемых модулей, что технически реализуется близкими методами. При этом
при динамической перегрузке особое внимание уделяется установлению
динамических связей.
При выполнении практической отладки ассемблерных программ
рекомендуется кроме разрабатываемого целевого программного модуля
разработать также контрольный пример или отладочный модуль, по выполнению
которого можно было бы судить о пригодности разработанного функционального
модуля к использованию.
Язык Ассемблера в программировании информационных и управляющих систем /О
3.5.4. Присоединение среды языка С
При использовании модулей библиотеки языка С в главной программе,
написанной на языке Асслера, необходимо правильно установить среду. Для
надежной работы с модулями библиотеки языка С перед их вызовом
необходимо установить окружение или среду языка с помощью служебных функций.
Проще всего это выполняется с использованием стандартной входной точки
библиотеки _astart. Модуль _astart выполнит установку окружения, включая
загрузку сегментных регистров, указателя стека, эмулятора сопроцессора с
плавающей точкой при необходимости, а также обращение к функции с именем
main и возврат в MS DOS после выхода из функции main. Кроме того, из
изложенного ранее видно, что в процессе загрузки программы среда должна
передать также и список аргументов из командной строки. Необходимость загрузки
среды приводит к невозможности непосредственного вызова прикладной
программы, составленной на любом языке высокого уровня как подпрограммы.
При выходе из программы main происходит возврат к программе управления
средой, которая своими заключительными действиями восстанавливает
прежнюю конфигурацию системы.
3.5.5. Специальные модули для эксплуатации с
языками высокого уровня
В системных программах особое место занимают программы
специального типа, называемые реентрантными или по старой терминологии IBM/360 -
реентерабельные, то *есть такие, которые допускают возможность повторного
входа в них параллельного вычислительного процесса еще до того, как
закончится обработка предыдущего ее вызова. Такие программные модули
позволяют хранить в памяти всего лишь одну свою копию, что оказывается очень
важным для программ, работающих со сложными объектами. Главными
требованиями к таким подпрограммам являются:
• автономность внутренних данных подпрограммы, что в большинстве
реализаций языков высокого уровня достигается путем размещения
локальных данных в стеке;
• запрет модификации кода подпрограммы;
• запрет повторного входа в критические фрагменты изменяющие
глобальные данные, что требует использования блокирующих примитивов.
При программировании на Ассемблере ответственность за соблюдение
этих требований возлагается на программиста, что сравнительно легко сделать
располагая рабочие данные в стеке и используя для разметки относительных
адресов в стеке структурного блока, который будет описан далее.
Другая сторона эксплуатации таких программ, связанная с
возможностью множественного доступа из разных программ может быть реализована
путем их оформления как программных прерываний и динамически
загружаемых модулей, которые также будут рассмотрены позже.
76 Глава 3 Техника модульного программирования на языке Ассемблера
Рекурсивные подпрограммы хотя и имеют другую природу, связанную с
рекуррентным представлением функциональных зависимостей, их можно
рассматривать как частный случай реентрантных программ в случае повторного
входа из того же вычислительного процесса. При этом первые два требования
сохраняют свою силу, а третье может быть снято или заменено запретом
использования глобальных переменных.
3.5.6. Работа с ассемблерными вставками и вызовами
функций MS DOS
При использовании систем Borland Pascal версий 6.0 и выше и в
соответствующих C/C++ системах текст на Ассемблере, ограниченный директивами
ASM/END, просто размещается в основной программе:
asm
Программа на Ассемблере
end;
Такая вставка должна выполняться со строгим соблюдением аксиом
генерации объектных кодов компилятором. Типовые аксиомы могут быть
сформулированы следующим образом:
• результаты выполнения операторов языков высокого уровня
формируются в аккумуляторе и в момент завершения оператора фактически
использованы и не нуждаются в дополнительном сохранении;
• вызываемая программа может изменять любые регистры за
исключением сегмента стека SS указателя стека, сегмента глобальных данных DS и
обязательно изменяет регистры, через которые возвращаются результаты.
Из изложенного вытекает, что в ассемблерных вставках практически
безболезненно может использоваться аккумулятор, а остальные регистры
должны перед использованием сохраняться, а в конце вставки
восстанавливаться.
Вызовы функций MS DOS имеют организацию, которая не совпадает с
организацией вызова подпрограмм в языках высокого уровня. Во-первых, для
обращения к таким подпрограммам используется команда обращения к
прерываниям в форме INT vect, которая позволяет обращаться к системным
подпрограммам фактически без знания адресов входных точек функций DOS. Входные
аргументы функций MS DOS задаются в регистрах в основном с соблюдением
следующих назначений:
АН - номер функции MS DOS;
AL - номер подфункции;
ВХ - аргументы размеров объектов и дескрипторы;
СХ - управляющие атрибуты;
DS:DX - строковые аргументы или указатель на блок данных;
ES - сегментные адреса.
Язык Ассемблера в программировании информационных и управляющих систем II
Результаты, как правило, возвращаются в АХ, а в бите CF признак
корректного завершения функции MS DOS.
Краткие итоги
По возможностям модульного программирования язык Ассемблера не
уступает любому из языков высокого уровня, однако при нестандартной
передаче параметров или организации модуля требуются существенные
дополнительные трудозатраты программиста.
ГЛАВА 4
МЕТОДИКА ЭФФЕКТИВНОГО
ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ АССЕМБЛЕРА
В шой г пав? рас с мотрены общие методы и основные щте
мы эффективного программирования на языке Ассемблера в
основном дпя программ вычислительного типа,
шпользуемых для расчетов чис ленных значений в ра тсюб разных
информационных и управляющих системах Кчючсвымп мо
ментами программирования на )пюч уровне явчяютс я
эффективное использование запоминающих ресурсов
(регистров и г частой памяти) и операционных ресурсов (прежде
всего, на уровне системы команд) центрального процессора
4.L Общая методика декомпозиции задач при
составлении программ
Хотя до сих пор процесс программирования рассматривается как
творческий, существуют многочисленные рекомендации по формальному контролю
и доказательству правильности преобразований (верификации) на этапе
проектирования программ. Контроль результатов выполнения (тестирования)
программ на контрольных примерах в процессе их проверки и эксплуатации также
чаще всего выполняется путем формального сопоставления рузультатов. При
формализованном построении программ программист получает возможность
анализа собственных преобразований в нескольких направлениях: абстракция
данных, ограничение данных, методы и алгоритмы вычислений. Основы
формализации процесса программирования заложены в таких технологиях и
стилях программирования, как структурное, модульное,
объектно-ориентированное программирование. Однако в сфере искусства до сих пор остаются методы
построения и подбора оптимальных и обобщающих программ.
Разработка программ по методу преобразования связей аналитической
модели предметной области, определения целей задач информационных
преобразований и декомпозиции задач на последовательность действий по
последовательному приближению к конечной цели выполняется в соответствии со
стандартной последовательностью действий:
1) определить структуры входных и выходных данных и
информационные ресурсы (память или регистры) для их сохранения;
2) определить спецификацию программы: аналитические связи между
входными и выходными данными, цель преобразований программ и
требования к диапазону обрабатываемых данных;
3) определить ограничения на структуры и размеры данных,
определяемые спецификационными требованиями к функционированию программы;
Язык Ассемблера в программировании информационных и управляющих систем 79
4) определить комплекс ресурсов вычислительной системы, доступных
для решения задач, начиная с пакетов программ, включая стандартные
процедуры, функции и операторы языков программирования и операционной
системы и кончая машинными командами целевого процессора;
5) выбрать за базовый наиболее мощный ресурс, с помощью которого
можно решить поставленную задачу или приблизиться к поставленной цели;
6) определить структуру декомпозиции главной задачи и отличия от
примененного для ее решения базового ресурса, которые в дальнейшем будут
рассматриваться как остаточные или частные цели решения задач;
7) доопределить блоки для выбранного варианта декомпозиции.
Эту последовательность из семи действий необходимо рекурсивно
выполнять для всех недоалгоритмизированных остатков задач. Наиболее важной
частью рассмотренного процесса является определение базовых ресурсов
декомпозиции и определение их параметров как фрагментов программ. Ресурсы
. . определяются на базе традиционных
принципов структурного
программирования и проектирования
программ путем анализа задачи сверху
вниз. Такие простые виды
декомпозиции, как последовательная и
параллельная требуют сходства
отдельных частей программы с
наличQ
1
р
1
1 ,
Q
1
т
ными операционными ресурсами и имеют в базовом варианте две структурных
составляющих: блоки Р и Q. Если выходные данные каждого из блоков не
являются входными данными другого блока, то декомпозиция рассматривается как
параллельная.
Более сложные
варианты структурных
блоков при декомпозиции
выбираются по множеству
традиционных операторов
выбранного базового
языка программирования.
Условная декомпозиция
возникает как следствие
условных ограничений
интервалов значений и
интервалов корректности
математической модели в
спецификации связей пред-
т метной области. Контро-
' лируемое условие
определяется логической связью С, проверка которой на Ассемблере командой
условной передачи управленияй приводит к разветвлению программы на блок Р -
80 Глава 4 Методика эффективного программирования на языке Ассемблера
обработка по положительному результату проверки условия и блок N -
обработка по отрицательному результату.
Для многочисленных вариантов условной декомпозиции базовыми
управляющими структурами являются if- и case-операторы языков высоких
уровней, которые с одной стороны
могут быть следствием определения
диапазонов значений переменных, а
другой - следствием применения
условных структур данных типа record
с использованием case в языке Pascal
и union - в языке С. В языке
Ассемблера if-декопозиция включает
проверку условий путем вычисления
выражений и анализа признаков
результатов, легко реализуемого командами условных переходов, для которых
тип используемой операции определяется типом обрабатываемых данных.
Циклическая декомпозиция связывается либо с рекурсивными
аналитическими связями или с повторяемыми структурами данных задачи. Основным
классификационным признаком циклов является организация проверки
условия окончания цикла С/. Различают типы циклов с предусловиями и
постусловиями произвольной логической сложности, циклы, управляемые значениями
параметров цикла, и циклы с контролем допустимого отклонения результатов,
либо целевых переменных и с использованием специальных индикаторов. В
качестве параметров цикла чаще всего используют индексы или относительные
адреса, циклы с контролем содержимого счетчиков с заранее известным числом
циклов.
Развитие и расширение ресурсов программирования может
выполняться, как в процессе проектирования программного обеспечения сверху вниз
вследствие целенаправленной декомпозиции задач, так и вследствие анализа
ранее разработанных программ в проектировании в направлении снизу вверх
путем накопления все более мощных программных ресурсов, позволяющих
достичь более серьезных целей. Третье направление развития программных
ресурсов может быть определено как совершенствование или обобщение
программ для их применения к более мощным и общим структурам и форматам
данных. Расширенные программные ресурсы организуются как фрагменты
программ в форме макроопределений или как процедуры, функции или задачи
работающие с данными обобщенных форматов. Макроопределения,
определяющие прообразы или прототипы фрагментов программ, при генерации
макрорасширений по макровызову (макрокоманде) дают результаты, близкие к
функциям типа inline в языке С. При таком подходе к построению расширенных
ресурсов нет принципиальной разницы в использовании процедурных языков
высокого уровня и Ассемблера.
Изложенное выше акцентирует внимание на подборе операционных
ресурсов, следствием из которого является стратегия оперативного
распределения таких специализированных запоминающих ресурсов, как регистры.
Программист на Ассемблере должен с особым вниманием следить за эффективным
Язык Ассемблера в программировании информационных и управляющих систем 81
использованием регистров. Сохранение в них промежуточных результатов во
многих случаях позволяет сократить число пересылок данных. Более того, из
регистров может быть составлен некоторый виртуальный стек, с более
эффективной работой, что можно было заметить в предыдущей главе, сравнивая
различные методы передачи параметров. Такой подход нашел завершенное
воплощение в организации регистров математических сопроцессоров семейства ix87.
Определенное значение имеет и рациональное использование разрядной сетки
регистров процессора, но здесь важно отметить различия в переходах от 8- к
16-битовым регистрам и при переходе от 16- к 32-битовым регистрам. В
последнем случае распаковка данных может выполняться сдвигами или перезаписями
через память.
4.2. Общая методика повышения эффективности
программ с помощью средств Ассемблера
Как уже было отмечено для каждого шага структурированной
декомпозиции выбираются соответствующие операционные ресурсы. Язык Ассемблера
имеет более разнообразные и эффективные возможности анализа условий и
передачи управления по сравнению с любым языком высокого уровня, но в то же
время требует решения дополнительной проблемы комплексирования команд и
выбора наилучшего варианта. Для case-структур с количеством вариантов до
256 можно использовать сочетание команд:
XLAT ; Преобразование функции в индекс
XOR АН,АН ; Очистка старших разрядов
SLL АХ,1
MOV ВХ,АХ ; Копирование индекса
JMP CASE_JMP[BX] ; Коственный переход по массиву адресов
Здесь CASEJMP представляет собой массив внутрисегментных адресов
переходов на ветви конструкции типа case. Такой прием может быть применен
и в более общих случаях классифицирующих или маркирующих массивов и
таблиц, в которых определяется относительно небольшое количество ветвей
для гораздо более широкого диапазона значений входных данных. Интересно
отметить, что подобный прием может применяться и на аппаратном уровне в
специализированных системах для быстрого принятия решений с помощью
маркирующей памяти, использующей механизм прямого доступа для быстрого
поиска.
Типичный операционный ресурс для построения циклической
программы с последовательными повторениями, количество которых предварительно
вычислено имеет вид:
MOV CXг количество повторений ;Подготовка счетчика
L&P:P ; Выполнение базового повторяемого блока
LOOP L&P ; Переход на повторение, если счетчик
; не обнулен.
О4. Глава 4 Методика эффективного прог раммирования на языке Ассемблера
Отметим, что диапазон перехода при такой организации цикла
ограничен -128 бантами относительно адреса следующей команды во всех режимах
процессоров ix86. и потому при программировании тела цикла лучше всего
пользоваться обращениями к процедурам. Таким образом, в этом случае
слабость в системе команд стимулирует переход к прогрессивному модульному
программированию.
Еще одна особенность выполнения команды LOOP является
возможность использования в 32-разрядном режиме регистра ЕСХ, применение
которого обусловлено атрибутом размера адреса, и если в защищенном
32-разрядном режиме это происходит автоматически, то в расширенном реальном
режиме перед командой LOOP необходимо задагь этот режим префиксом 32-разрад-
ного адреса в прел шествующей директиве db.
Рассмотрим, как простейший пример, задачу преобразования констант,
представленных в символьной форме (код ASCII или КОИ-8) в форму
внутреннего представления в ВК. В данном случае форма входных данных является
стандартной. При программировании на Ассемблере, в отличие от языков
программирования высокого уровня, программист отвечает за эффективное
использование ресурсов процессора. Учитывая особенности команд процессоров
ix86, обрабатываемую символьную информацию целесообразно определить
как входную строку данных по адресу, записанному в DS:SI, и длиной,
помещенной в регистре СХ.
Формат результата определим гак, чтобы использовать стек как
приемник результата с традиционной дчя процессоров фирмы Intel обратной
последовательностью записи байтов слова по адресу, содержащемуся в регистрах
SS:BP. Аналитическая связь для преобразования числа из / цифр и основанием
а можно определить в виде:
£[,] — s[i-\\ * Я -*" d\,} - г#с Ц/] ~ значение очередной цифры,
Л'г; м- значение числа из /-и цифры.
Рекурсивный характер зависимости и соответствующая повторяемая
структура входных данных вызывает необходимость циклической
декомпозиции с основной операцией циклического определения частичного произведения
с прибавлением очередной цифры.
lbc:LODSB ; Размещение очередной цифры
XOR АХ,АХ ; Очистка старших разрядов
MOV BX,OFFSET ТВ ; Размещение адреса таблицы
XLAT CS:TB ; Перекодировка
MOV BX,RADIX ; Размещение основания системы счисления
CMP AL,BL ; Сравнение цифры с основанием
JL lcorr ; Переход по допустимому значению
Фиксация ошибки
lcorr: Накопление суммы частичных произведений
LOOP lbc ; Переход на циклическое повторение
Язык Ассемблера в программировании информационных и управляющих систем 83
Начальные значения регистров DS:SI, используемые в программе, а
также область памяти RADIX, определяющая основание системы счисления,
должна быть подготовлена в блоке начальных пересылок или в
последовательности команд для обращения к подпрограмме. Базовый фрагмент цикла в
программе определен необходимостью достаточно универсальной формы
выходных данных, которая позволяет определить размер данного и разместить
результат в зарезервированных байтах. Условимся формировать результат в
форме: двухбайтный размер и массив из соответствующего количества слов.
Специфическим ограничением программы размером в 64 Кбайтов можно
пренебречь для большинства ее применений. Более актуальны в реальном режиме
ограничения системных ресурсов стека размерами сегментов снимаются
общесистемными средствами контроля за особыми ситуациями.
Вычисление очередного частичного произведения и накопление суммы:
Подготовка начального индекса
Сохранение содержимого регистра f
Обмен с частью частичного
произведения
Умножение на основание
Восстановление содержимого регистра f
Накопление суммы младших разрядов
Пересылка старших разрядов
Сохранение флага переноса
Вычисление индекса текущего байта
Контроль наличия свободных
разрядов суммы
Переход, если не конец
Восстановление содержимого регистра f
Добавление последнего переноса
Переход, если нет дополнительных разрядов
Контроль предельной длины полей данных
Переход, если размер не исчерпан
Фиксация ошибки
lnlim:SUB BP,2 ; Расширение поля стека выходных данных
MOV [BP-2],DI ; Фиксация нового размера
ls:XCHG AX,[BP][DI] ; Смещение в сторону младших разрядов
SUB DI,2 ; Декремент индекса
JNS Is ; Переход, если результат не смещен до конца
Интересно отметить, что применение команд LOOPc, где с - условие
равенства или неравенства, или изоморфные им условия нуля и не нуля,
оказывается удобным при применении в цикле сравнений, проверяющих
дополнительные условия продолжения обработки, например, проверку границы
обрабатываемых данных.
XOR
DI,DI
PUSHF
ln2:XCHG AX,[BP][DI]
MUL
POPF
ADC
MOV
BX
[BP][DI],AX ;
AX,DX
PUSHF
LEA
CMP
JNA
DI,[DI+2]
DI,[BP-2]
In2
If:POPF
ADC
JZ
CMP
JNA
AX,0
lnl ; Пере
DI,LEN_DAT ;
lnlim
84 Глава 4 Методика эффективного программирования на языке Ассемблера
4.3. Повышение эффективности вычислительных программ
Основными принципами получения эффективных программ процессора
являются эффективное распределение и эффективное использование ресурсов
процессора, выполняемое программистом в процессе построения программ.
Исторически сложились три базовые формы представления программ:
• абсолютные коды команд, размещенные в фиксированных ячейках
памяти;
• переместимое представление машинных команд для ее последующей
компоновки;
• программа на внутреннем языке некоторой кажущейся (виртуальной)
машины (ВМ), ориентированной на реализацию языка
программирования высокого уровня или специфических проблемных задач предметной
области.
Семантические программы, реализующие конструкции процедурно- и
функционально-ориентированных языков, обычно строятся из набора
шаблонов, которые наиболее эффективно реализуют подобные действия. Для
специфической реализации ВМ обобщенной предметной области также может
строиться набор операций на уровне процедур. Однако для более
фундаментального и формализованного анализа задачи получения эффективных кодов
необходимо опираться на формальную модель ресурсов целого компьютера или ВМ.
В ее состав должны входить:
• модель информационных или запоминающих ресурсов, включая память
и программно-доступную архитектуру процессора;
• модель операционных ресурсов процессора в виде таблицы команд
объектного процессора.
Очевидны локальные действия по оптимизации кода - минимизация
пересылок, использование для работы со сложными структурами данных
указателей вместо полноформатных аргументов.
Так как распределение памяти в Ассемблере ix86 ведется по сегментам,
то их размещение в памяти при загрузке становится достаточно простой
задачей. Общая схема распределения ресурсов памяти включает правила
распределения скоростных регистров, которы большинство современных процессоров
используют для сохранения промежуточных результатов.
Задача эффективного распределения главной памяти при
программировании на Ассемблере с учетом современных тенденций программирования
решается следующими действиями:
• систематизация структур данных для представления экземпляра объекта
в одной локальной области памяти;
• систематизация глобальных данных в соответствии с их размером и
назначением;
• последовательное размещение данных в соответствующих сегментах.
Язык Ассемблера в программировании информационных и управляющих систем 85
Локальные данные процедур и функций чаще всего подобным же
образом размещают в стеке. Хотя задачу эффективного использования этих
регистров можно рассматривать как задачу распределения памяти, изучим ее с учетом
локальности распределения регистров во времени.
При разработке эффективных программ необходима аналитическая
работа, связанная с переупорядочиванием и изменением операций в программе-
прототипе на языке высокого уровня с целью получения более эффективной
объектной программы. Для достижения эффективности необходимо в
совершенстве владеть типовыми рациональными приемами выполнения типовых
декомпозиций для всех используемых в ВК типов данных. Лучшие образцы
компиляторов содержат подобную информацию во встроенной информационной
базе генератора кодов, что позволяет им создавать объектные коды сложных
программ, не уступающих по качеству программам, написанным на языке
Ассемблера программистами высокой квалификации, но со значительно
меньшими затратами на программирование.
Критерии повышения эффективности или оптимизации программ могут
быть упорядочены по составляющим. При этом самые распространенные
минимизирующие составляющие критериев эффективности программ это:
• минимум затрат памяти центрального процессора и внешней памяти по
ее категориям;
• минимум затрат времени центрального процессора.
Большинство шаблонов эффективных элементов программ повышают
их эффективность в этих двух направлениях. Однако в управляющих системах,
работающих с фиксированными и ограниченными блоками памяти часто
используют ограничительные составляющие критериев. Кроме того, в наше
время при проектировании специальных программ обращают внимание на такие
дополнительные составляющие критериев, как:
• получение нужной точности результатов;
• обеспечение заданной надежности и достоверности результатов при
ограниченной достоверности входных данных.
Для разработки эффективных программ используют следующие
категории преобразований, оптимизирующих программы:
1) эквивалентные преобразования аналитических формул спецификации
предметной области в формулы, определяющие вычислительные действия;
2) упрощение формул, определяющих значения целевых переменных
эквивалентными и приближенными методами;
3) преобразования для реализации фрагментов выражений
эффективными комбинациями машинных кодов;
Методы третьей категории должны обеспечить эффективное
использование ресурсов целевой ЭВМ на этапе выполнения. Здесь используются три
основные типа приемов программирования:
1) эффективное использование запоминающих ресурсов (регистров и
главной памяти) целевого процессора;
об Глава 4 Методика эффективного программирования на языке Ассемблера
2) эффективное использование операционных ресурсов (системы
команд) процессора;
3) эффективное кодирование данных, не вписывающихся в стандартные
форматы процессора, их структур и комплексов, особенно в слабо
формализованных областях.
Наиболее общий метод распределения запоминающих ресурсов состоит
в том, что все наличные запоминающие ресурсы общего назначения
разделяются на следующие категории:
• регистровая или сверхоперативная память для каждого формата
данных, включающая R регистров;
• оперативная память размером М ячеек, чаще всего по одному байту, на
которую проектируются данные всех форматов;
• дисковая или остаточная виртуальная память размером V байтов.
Здесь простейшее распределение памяти выполняется по числу
необходимых и наличных элементов через последовательное выделение ресурсов по
счетчику и через отметки занятости-освобождения ресурсов. Более сложен
подход с выделением специализированных ресурсов, но для него более естественна
оптимизация с помощью эффективных прототипов. Так в процессорах ix86, по
крайней мере в реальном режиме, имеет место достаточно сложная
специализация регистров общего назначения, различающая аккумуляторы, укороченные и
расширенные регистры данных, счетчики, базовые и индексные регистры,
которые именно в этом качестве могут использовать только особые команды.
Распределение регистров при программировании на Ассемблере удобно
выполнять для локального участка программы. В первую очередь нужно
распределить специализированные регистры, если в их использовании есть
необходимость. Оставшиеся регистры нужно распределять для хранения наиболее
часто используемых значений. Значения однократного пользования лучше
оставлять в памяти. Аккумулатор удобнее выделять для хранения оперативных
результатов и рассматривать его как буфер перед стеком. Свободные регистры
можно рассматривать как привелегированнй стек для часто используемых
данных.
Для эффективного использования специализированных операционных
ресурсов необходимо контролировать их динамическую разметку и занятость
регистров промежуточными результатами. Оптимальное распределение
ресурсов начинается с проверки возможности применения наиболее эффективных
групповых операционных ресурсов, таких как команды перехода по счетчикам,
и команды обработки цепочек байтов, слов и т.д., и заканчивается проверкой
элементарных канонических ресурсов. Неэффективность многих компиляторов
обусловлена в большинстве случаев именно слабой выходной лексикой и
связанной с этим невозможностью генерирования мощных групповых команд и их
эффективных комбинаций.
Типичным примером слабости выходной лексики компиляторов можно
считать типичную аксиому результата умножения целочисленных данных
размером более одного байта, которая определяет результат умножения с тем же
Язык Ассемблера в программировании информационных и управляющих систем 87
типом, что и его аргумент максимального размера. Операции умножения MUL
и IMUL системы команд ix86 по принципу минимизации потерь информации
всегда создают результат двойной длины и размещают его в расширенных
аккумуляторах DX:AX для 16-разрядного умножения и EDX:EAX - для
32-разрядного. Такие операции удобно применять при различных видах
аппроксимаций измерительных данных в измерительных и управляющих системах, чаще
всего при линейной интерполяции. Для этого организуются операции
сравнения большей длины или восстановление физических значений данных по
линейно-аппроксимированным характеристикам датчиков.
Просмотр отдельных участков с выбором требуемого участка по
значениям аргументов может быть реализован с помощью процедуры IvSrc,
рассмотренной в предыдущей главе. Здесь интересно отметить некоторое
неудобство, вызванное несовершенством групповых операций над кодами, не
позволяющими различать отношения типа больше-меньше, что несколько удлиняет
программу.
Еще один интересный момент программирования управляющих систем
связан с использованием масштабированных данных с фиксированной точкой.
Работа с такими данными, как правило, выполняется существенно быстрее
обработки данных с плавающей точкой. Однако сопутствующие проблемы
масштабирования, а также необходимости надежного и эффективного
использования разрядной сетки затрудняет их применение. Средства
автоматизированного решения этих проблем не включаются в языки Ассемблера и языки
программирования высокого уровня со времен IBM/360 и PL/I, скорее всего, для
стимулирования приобретения более дорогостоящих систем с плавающей точкой. В
настоящее время, когда практически все процессоры ix86 имеют встроенные
математические сопроцессоры выбор форматов измерительных данных стал
чисто программистской проблемой.
Еще одна сторона разработки эффективных программ связана с
выбором кодов специальных или кодированных данных. Эта задача с давних времен
практически полностью отдана на откуп или произвол программистов.
Появление пронумерованных типов данных (тип enum языка С от слова enumerated),
которые часто не очень точно переводят как перечислимые, только лишь
облегчило и частично автоматизировало выбор внутреннего кода по спецификации
составленной программистом, что практически никак не связано с
эффективностью кодов обработки этих данных.
Эффективное кодирование данных перенумерованных типов
используется довольно редко и является заботой программиста, начиная с
конструирования собственных типов данных, до выбора средств их эффективной
обработки. Здесь особую роль играет выбор таких кодов, которые допускают проверку
отношений такими традиционными машинными операциями, как
арифметические отношения, отношения вхождения, проверяемые по маскам и
комбинированные отношения. В системах искусственного интеллекта эти задачи
усложняются в связи с необходимостью построения отношений унификации для таких
разных преобразований системы координат, как масштабные преобразования и
повороты. Однако их использование требует специальных процессоров новых
типов.
ОО Глава 4 Методика эффективного программирования на языке Ассемблера
Однако для получения эффективных программ нужно обращать
внимание и на машинно-независимую эффективность программ, забота о которой в
таких языках высокого уровня, как Pascal, С и др. может быть переложена на
компиляторы. В этом случае используют четыре основных метода:
• свертка, т.е. выполнения операций, операнды которых известны при
компиляции;
• исключение лишних операций или повторяемых вычислений (в
основном за счет однократного программирования общих подвыражений),
включая циклические вычисления;
• замена сложных операций более простыми за счет эквивалентных
преобразований;
• эквивалентные преобразования графовой модели программы.
Требуемая степень оптимальности, проектируемой программы
определяется предполагаемыми условиями ее эксплуатации и зависит от многих
факторов. Для программ, используемых редко, высокий уровень оптимизации не
обязателен.
Машинно-независимая оптимизация проще всего выполняется в
пределах линейных участков или последовательностей команд. Например,
последовательность операций, выполняющая присваивания любого вида создает
линейный участок. Внутри линейных участков обычно выполняют свертку и
устранение лишних операций. Более сложной задачей является выполнение
эквивалентных и адекватных преобразований на линейных участках с целью
упрощения аналитических выражений при условии обеспечения достаточной
точности и надежности решения задач. До последнего времени эти преобразования
были заботой аналитика предметной области, в которой решается задача, но в
связи с бурным развитием средств искусственного интеллекта вырисовывается
возможность использования автоматизированных методов аналитических
преобразований в компиляторах.
4.4. Программирование ветвлений и логического вывода
Для эффективного программирования ветвлений, задаваемых в языках
высокого уровня операторами типа if-then-else следует учитывать особенности
реализации условных переходов с помощью машинных команд и особенности
реализации условных операторов и операторов вычисления логических
выражений в языках высокого уровня.
Ключевым моментом в программировании условных операторов на
языке Ассемблера является понятие признаков или флагов результатов
арифметических, логических и специальных операций. Для вычисления логических
выражений могут использоваться логические операции, которые практически в
любом процессоре выполняются как поразрядные с формированием групповых
признаков результатов.
Проверяемые признаки результатов для арифметических операций
соответствуют результату последней операции, чаще всего формируемому в акку-
Язык Ассемблера в программировании информационных и управляющих систем 89
муляторе. То есть при проверке условий относительно нуля нет необходимости
выполнять сравнение с нулем, а можно использовать признаки результата
сформированные при вычислении выражения. В зависимости от типа
выражения выбирается тип команды перехода. Так команда: JNA метка после
вычисления арифметического выражения выполнит переход, по условию не больше
или меньше, или нулю для выражений беззнакового типа, а команда JNG метка
по такому же условию - для выражений над целыми данными со знаками.
Следовательно, при программировании на Ассемблере удобно
приводить сложные отношения к форме отношений относительно нуля.
Здесь важно отметить, что реализация действий, заданных после
ключевого слова else неизбежно сопряжена с необходимостью использования
машинной команды JMP после фрагмента then, то есть даже реализация
структурированных операторов языков высокого уровня неизбежно приводит к
использованию неструктурированных команд на машинном уровне.
Если же сложные отношения включают в себя логические операции, то
необходимо учитывать способы построения этих условий. Например,
логические операции & и | в языке С соответствуют поразрядным машинным
операциям и их выполнение в условных выражениях в принципе будет таким же, как и
для арифметических операций. Операции над логическими или булевыми
операндами && и || требуют определения специального типа логического значения.
В терминах машинных представлений логический нуль удобно
представлять традиционным или арифметическим нулем, тогда любое ненулевое
значение можно интерпретировать как логическую единицу. Такой подход удобен
при проверке условий относительно нуля, что успешно применяется в языке С,
но создает дополнительные трудности при реализации логических операций
над булевыми данными. Поэтому, для определения булевого значения обычно
используют либо один разряд, чаще всего старший, либо специальное значение,
например, "-1". Тогда после относительно простого действия по
преобразованию результата в булевскую форму, например, для:
mov al,0
jna $+4
mov al,-l
формирование признака логической единицы мы получим булевский результат
отношения >0 для, обработанного перед этим, выражения. Подобные
результаты могут обрабатываться байтовыми логическими операциями.
Еще одним интересным моментом является машинная реализации
аксиомы С для вычисления булевских выражений, которая гласит, что
вычисление цепочки булевских операций можно прекратить, если ее результат
становится ясным до полной обработки. То есть при обработке цепи конъюнкций
появление нуля в промежуточном результате, говорит об окончательном
нулевом результате, как появление логической единицы в цепи дизъюнкций говорит
об окончательном единичном результате. Например, для дизъюнкций можно
записать:
90 Глава 4 Методика эффективного программирования на языке Ассемблера
; вычисление выражения п
jnz LtNchn
; вычисление выражения п+1
jz $+4
LtNchncmov al,-l ; установка логической единицы.
Задачи логического вывода, занимающие ключевые позиции в
искусственном интеллекте, решаются чаще всего либо с помощью продукционного
метода, который сравнительно просто реализуется через запись условий в
операторах типа if-then, либо с помощью средств логики предикатов, в которой
особое значение приобретают понятия неопределенных объектов и
унифицируемости объектов. Реализация этого подхода требует использования нескольких стеков.
4.5. Типы циклов и их программирование
Основные типы циклов строятся по аналогии с языками высокого
уровня, но самые простые получаются с использованием счетчиков. Другая группа
практически полностью изоморфна операторам циклов из языков
программирования высокого уровня.
4.5.1. Программирование циклов с помощью счетчиков и
команд условных переходов
Хотя выше были приведены примеры организации циклов с командами
типа LOOP обратим внимание на более общий, но менее эффективный подход.
В качестве счетчика обычно используется один из регистров общего
назначения, например - СХ, либо пара регистров, например - СХ и DX. Количество
итераций записывается в регистр-счетчик, затем с помощью команд DEC или
SUB организуется уменьшение счетчика. Контроль завершения цикла
выполняется командой условного перехода JNZ. Обобщенная структура цикла:
гапи_1оор equ 4096 ; количество повторений цикла
MOV CX,num__loop ; инициализация счетчика цикла
start_loop:
; начало тела цикла
. \
} тело цикла
DEC CX
JNZ start_loop
JCXZ end_loop
JMP start loop
уменьшение счетчика цикла
контроль завершения цикла
либо вместо JNZ 2 команды
выход, если СХ=0
переход на начало цикла
end_loop:
Язык Ассемблера в программировании информационных и управляющих систем Q1
Если количество повторений превосходит 65535, то в качестве счетчика
цикла используется пара регистров, например (DX - СХ). Уменьшение
счетчика организуется следующим образом:
DEC СХ
JNZ $+3
DEC DX ; уменьшение пары (DX - СХ) на 1
Контроль завершения цикла:
JCXZ 1Ы ; выход из цикла
JMP start__loop ; переход на начало цикла
lbl:TEST dx,dx ; контроль старших разрядов счетчика
JNZ start_loop
Отметим, что в теле цикла необходимо сохранить текущее значение
счетчиков цикла, в последнем примере СХ и DX, если их использование
необходимо, например, для вложенных циклов. Кроме того, для всех циклов, кроме
рекурсивных, внутри его тела необходимо соблюдать баланс всех стеков
(памяти центрального процессора и регистров сопроцессора) для корректной работы
программы. То есть в простейшем случае количество команд PUSH и POP
внутри цикла должно быть одинаковым с учетом размеров регистров.
Часто требуется завершить цикл при выполнении определенных условий
до того, как содержимое регистра СХ достигнет нуля, например, если при
просмотре цепочек кодов обнаружен элемент, являющийся признаком конца
обработки. Такие циклы организуются с помощью команд LOOPE/LOOPZ и
LOOPNE/LOOPNZ. Команда LOOPE уменьшает содержимое регистра СХ на
1, а затем осуществляет переход на смещение, заданное в операнде, если
содержимое регистра СХ не равно 0 и флаг ZF равен 1. Таким образом, цикл
завершается, если содержимое регистра СХ равно 0, либо флаг ZF равен 0, либо оба
они равны 0. Команда LOOPNE уменьшает содержимое регистра СХ на 1, а
затем осуществляет переход, если содержимое регистра СХ не равно 0 и флаг ZF
равен 0. Таким образом, цикл завершается, если содержимое регистра СХ
равно 0, либо флаг ZF равен 1, либо будет выполнено и то и другое. Общее
количество циклов не превосходит загруженного в регистр СХ перед началом цикла,
если внутри цикла регистр СХ не подвержен изменениям.
4.5.2. Программирование циклов с помощью анализа
специальных переменных
Этот метод основан на анализе побочных результатов машинных
команд и его использование в языках высокого уровня чаще всего неэффективно.
При разработке циклических программ для проверки условий
окончания циклов чаще всего применяются специальные индикаторы и оценки
величины ошибки. Контроль точности вычислений будет рассмотрен при решении
задач с использованием команд ix86 с плавающей точкой. А примерами
применения индикаторов могут служить флаг CF при распространении переносов и
совпадение результатов на последовательных шагах итеративных вычислениях.
92 Глава 4 Методика эффективного программирования на языке Ассемблера
lcOradc word ptr[bpj [di],0 ; перенос в старшие разряды
lea di,[di+2] ; наращивание адреса
jnc lcO ; на начало цикла
При вычислении квадратного корня в формате с фиксированной точкой
итерационными методами цикл может быть организован следующими
командами:
lbl: . . .
add ex,ax ; вычисление y[i]+x/y[i]
rcr ex,1 ; деление на 2
cinp dx,cx ; сравнение с y[i-l]
jnz lbl ; переход на начало цикла
4.5.3, Программирование циклов с логическими условиями
Программирование таких циклов практически не отличимо от блоков
ветвлений и в практике можно пользоваться соображениями, описанными
выше для команд условных переходов. Единственным отличием от
программирования управляющих структур типа if-then-else будет несколько другое
расположение команд условных и безусловных переходов. Так для цикла с
постусловием достаточно всего лишь одной команды перехода к первому оператору
цикла. При проверке предусловия составляется так называемая шапка цикла,
которой проверяется условие и при его невыполнении происходит переход на
первую команду, следующую за пределами цикла. В конце цикла ставится команда
JMP для безусловного возврата на начало повторений цикла.
Интересный случай представляет собой программирование цикла типа
for, в качестве параметра которого чаще всего используются индексные
величины и адресные смещения. Здесь эквивалентный цикл, использующий проверку
постусловия командой LOOP может быть построен только для цикла, параметр
которого убывает с шагом, равным единице. Для других случаев приходится
программировать эквивалентный цикл с усложненной шапкой, включающей
проверку предусловия для индекса или смещения адреса. В двухбайтном
режиме более эффективно использовать значение индекса, учитывающего размеры
шага элемента, однако в случае, когда массив выходит за пределы одного
физического сегмента, это приводит к дополнительным проблемам.
Использование байта SIB в 32-разрядном режиме снимает проблему
размера индекса для данных стандартных форматов и других данных,
занимающих 1, 2, 4 и 8 байтов, но для структур данных произвольных форматов эта
проблема остается.
4.6. Программирование обработки комбинированных и
структурированных данных
Для программирования задач, использующих данные, недопустимые
системой команд базового процессора, можно использовать специальные типы
Язык Ассемблера в программировании информационных и управляющих систем Q3
данных - структуры и записи. Запись представляет собой набор битовых полей,
объединенных общим именем. Каждое поле записи имеет свою длину,
вычисляемую в битах, не обязательно равную целому числу байтов. Записи в
программе определяются в 2 приема: объявление шаблона или типа записи
директивой RECORD имеющей формат:
Имя записи RECORD имя поля : длина [~ выражение] г . . .
а сами данные записи образуются при объявлении записи директивой, имеющей
вид:
[имя переменной] имя записи <[список значении]>
Директива RECORD определяет 8- или 16-битный шаблон записи,
определяющий одно или несколько полей, имя которого может использоваться при
определении конкретной записи. Каждое поле определяется именем, длиной в
битах и необязательным выражением, определяющим начальное значение
поля. Описания полей разделяются запятыми и располагаются слева направо в
порядке описания. Если суммарная длина полей меньше, чем 8 или 16, запись
расширяется до левой границы байта нулевыми битами, таким образом,
последний бит последнего поля всегда является самым младшим битом записи.
Если длина поля не менее 7 битов, в выражении может быть использован символ в
коде ASCII или КОИ-7. Например:
elem RECORD liter:7='В1,weight:4=2 ; Определение шаблона
; записи
table elem <>,<'Df,Х0>,<f0',8> ; Определение трех записей
Последняя директива создает переменную типа запись с 16-битным
значением и структурой полей, соответствующей шаблону elem. Если имя
отсутствует, транслятор только распределяет память. Список значений полей записи,
разделенных запятыми, указываются в угловых скобках < >. Каждое значение
может быть целым числом, строковой константой, выражением и должно
соответствовать длине единого поля. Скобки < > обязательны, даже если
начальные значения не определены. Если Ассемблер вместо значения обнаружит
правую скобку или пробел, будет использовано предыдущее значение поля,
определенное директивой RECORD, а если оно не указано и там, значение поля
будет неопределенным. Например: первые две записи table типа elem выглядят в
двоичной форме так:
liter weight
I I I
0000010000 1 000 1 0
0000010001001010
94 Глава 4 Методика эффективного программирования на языке Ассемблера
При обработке полей записей употребляются операторы WIDTH имя
поля, вычисляющий длину записи или поля записи в битах и MASK имя поля,
формирующий маску битов для выделения битов поля в записи. Бит маски
равен 1, если он принадлежит полю, и 0 - в противном случае. Например: MASK
liter - формирует двоичный код 00000111111ЮОООЬ и команды для выделения
значения поля char имеют вид:
MOV AX,table ; Копирование записи
AND AX,MASK liter ; Выделение поля
MOV CL,WIDTH weight ; Определение количества сдвигов,
SHR AX,CL ; при этом в AL записывается 'В'
Создается структура последовательных полей, объединенных общим
именем. Объявление структуры в программе на языке Ассемблера происходит в
2 этапа: объявление шаблона структурного блока директивами STRUC и
ENDS с уникальным именем и определение самой структуры или ее экземпляра.
имя структуры STRUC ; Начало шаблона (типа) структуры
описание полей::
=количество, типы и начальные значения полей структуры
имя структуры ENDS ;Конец шаблона (типа) структуры
Для описания полей употребляются директивы DB, DW, DD, DQ и DT,
определяющие длину поля и начальные значения, которыми инициализируются
поля при отсутствии соответствующих начальных значений в определении
структуры. Имя каждого поля структурного блока, если оно определено,
должно быть уникальным для отображения при программировании смещения поля
относительно начала структуры. Структурный блок может содержать только
описание полей и комментарии, что исключает вложенность структур. Под
шаблон структуры память не выделяется, а сами данные образуются при
определении структуры в соответствии с форматом:
[имя переменной] имя структуры <[список значении]>
Согласно такому определению создается переменная структурного типа
со структурой полей, соответствующей шаблону, определенному в структурном
блоке. В угловых скобках < > размещается список значений полей структуры.
Значения в списке, если их несколько, позиционно соответствуют полям и
разделяются запятыми. Каждое значение может быть целым числом, строковой
константой или выражением, тип которого должен совпадать с типом
соответствующего ему поля. Для каждого поля может быть задано одно значение.
Скобки < > обязательны, даже если начальные значения не определены. Если
ассемблер вместо значения обнаружит правую скобку или пробел, то будет
использоваться начальное значение поля, заданное при описании типа
структуры, а если оно отсутствует, значение поля будет неопределено.
Язык Ассемблера в программировании информационных и управляющих систем 95
Рассмотрим пример построения таблицы имен пользователя в
трансляторе с языка Ассемблера, поиск в которой выполняется по имени,
используемому как ключ поиска. Полезными для разрабатываемой программы данными,
возвращаемые после поиска являются сегмент, смещение, тип или размер и
количество объектов. Например, возьмем неупорядоченную таблицу в виде
массива подобных элементов, для которых сначала нужно создать структуру,
определяющую структурный тип данных TB_ELEM. В таких таблицах можно
организовать только линейный поиск, путем последовательных сравнений
аргумента поиска с ключевой частью элементов. Пример структуры и фрагмента
программы:
Структура элемента таблицы имен пользова-
1 ; 8-байтный литерный ключ: имя
; пользователя
Закодированный тип/размер
Номер сегмента в модуле
Смещение до 4 байтов для 80386
Количество элементов
Конец сегментного блока (описания
сегмента)
Начало сегмента таблицы
(ele-ell) /SIZE__EL ; Количество статических
; элементов
; Длина ключа
<*1п2 ' ,-1, 4, OFFSET 1п2,1> ; Элемент-метка
TBJELEM
теля
KEY
ТР SIZE
SEGM
OFFSET
NUMBER
TB_ELEM
ТВ SEG
EL_NUM
ARG LEN
ell
el2
STRUC
DB f
DW ?
DW ?
DD ?
DD ?
ENDS
SEGMENT
DW (el
DW 8
ТВ ELEM
ТВ ELEM
<TTB
,OFFSET TB,255>
eln
ТВ ELEM 1000 DUP (<>)
12
Элемент-пере-
; менная
Статическое резервирование
; памяти
Имя границы размещения элементов
Длина элемента: константа
периода трансляции
Конец сегмента таблицы
Начало сегмента данных
Пример аргумента поиска
Адрес аргумента поиска
Адрес первого элемента таблицы
__ _ ; Конец сегмента данных
Начальная подготовка к выполнению поиска:
LES DI,aarg ; Подготовка адреса аргумента
LDS SI,atab ;Подготовка адреса таблицы
MOV CX,EL_NUM ; Подготовка количества элементов
Циклическая программа поиска s
PUSH SI ; Сохранение адреса следующего элемента
PUSH CX ; Сохранение количества оставшихся элементов
PUSH DI ; Сохранение адреса аргумента поиска
MOV CX,ARG LEN ; Пересылка длины аргумента
ele EQU $
SIZEJEL EQU el2-ell
TB_SEG ENDS
CHG_DATA_SEG SEGMENT
arg DB 'key !
aarg DD arg
atab DD ell
CHG DATA SEG ENDS
96 Глава 4 Методика эффективного программирования на языке Ассемблера
REPZ CMPSB
POP DI
POP СХ
POP SI
JZ yes
LOOP 12
Использование группового ресурса сравнения
Восстановление адреса аргумента поиска
Восстановление количества оставшихся
элементов
Восстановление адреса следующего элемента
Выход, если аргумент поиска найден
LEA SI,SIZE_EL[SI] ; Переадресация
Циклический переход, если таблица
не исчерпана
yes: . . .
Успешный или неуспешный поиск отображается состоянием бита ZF.
Полезные данные можно получить по адресу, записанному в регистрах DS:SI
после завершения цикла. Для обращения к отдельным полям переменных
структурного типа по прямому адресу надо указать операнд в форме:
имя переменной структурного типа . имя поля
Если начальный адрес структурного блока находится в индексном или
базовом регистре, то имена полей структуры можно использовать как
смещение в индексном или базовом адресе.
имя поля [индексный или базовый регистр]
Например, такая команда перешлет в аккумулятор номер сегмента для
элемента, найденного в таблице предыдущей программой:
MOV AX,SEGM[DI] ; Пересылка по относительному адресу поля
Другой подход к использованию структур заключается в их
динамическом создании либо путем выделения части стека, либо получением ресурсов
памяти от ОС. В этом случае программист должен разместить и запомнить
значения отдельных полей в полученной памяти и определить базовый или индексный
адрес, после чего к полям структуры можно обращаться по имени определенному в
структурном блоке.
Технология разработки программ на языке Ассемблера требует
включения отладчиков, с помощью которых можно проанализировать состояние
процессора, памяти и внешних устройств во время выполнения ассемблерной
программы. Для отображения содержимого памяти следует указать логический
адрес нужной области, например, DS:2545, которому в языке Ассемблера
соответствует операнд ES:[2545] с явным заданием адреса. Для проверки корректности
работы программы необходимо сопоставить ожидаемые результаты с
полученными после выполнения программы. В случае получения ошибочного
результата, аварийного окончания, зацикливания или зависания необходимо найти
причины или локализовать ошибку. Быстрее всего информацию о месте ошибки
дает контроль частичных результатов по методу половинного деления неанали-
Язык Ассемблера в программировании информационных и управляющих систем 97
зированной части программы, когда размер зоны поиска сокращается в два
раза после каждого эксперимента.
Краткие итоги
Язык Ассемблера позволяет состовлять более эффективные машинные
коды за счет увеличения объема более эффективного неавтоматизированного
распределения ресурсов высококвалифицированным программистом.
Программист должен в совершенстве владеть архитектурой и системой команд
целевого процессора.
4 В.И. Пустоваров
ГЛАВА 5
ОСОБЕННОСТИ СИНТАКСИСА ОСНОВНЫХ
ВЕРСИЙ ЯЗЫКА АССЕМБЛЕРА
В этой /лаве <х поеное внимание акцентировано на с пипшкеиче-
ских (к обеншх тях (х новных трансляторов с языка при ос/юрм-
ленип щю/раммиых модулем и автоматизп\юванноп (е/мента-
ции, а также принципы применения мащкхрео\тв и директив
условной трансляции. Для п<хщх)ения м\х\х>,ктпвных кодов
этот раздел необязателен, но для MfxjwKmueuo/o комплексиро-
вания программ его необходимо изучить.
5.1. Макросредства в Ассемблере MASM
Основными средствами генерирования и модификации программ и их
фрагментов во время трансляции являются средства препроцессорной
обработки или макросредства. Термин "препроцессорная обработка" возник в связи с
тем, что чаще всего ее действия выполняются перед началом работы языкового
виртуального процессора, реализуемого компилятором или интерпретатором
языка. За десятилетия развития системного и прикладного программирования
были разработаны и успешно применялись встроенные, или автономные
универсальные макросредства, или макросы практически для всех языков
программирования. Сейчас наиболее активно используются средства обработки препроцес-
сорных утверждений в языках С и C++.
В языках типа Ассемблер роль и значимость макросредств периодически
изменяется. Так, макросредства в Ассемблере IBM/360/370 составляли основу
генерации операционных систем. Однако в настоящее время установочные или
инициализирующие фрагменты программ, называемые инсталляцией по
транслитерации английского термина installation, стараются строить на основе
выделения управляющих констант или сегментов памяти для выбора требуемых
вариантов вычислений в процессе выполнений или инсталляций. В современных
Ассемблерах [6,43, 53] м акр о средств а, как правило, сохраняются, и
используются для сокращения программ в соответствии с условиями их применения.
Они позволяют переносить в выходную программу блоки предложений
прототипов, задаваемых в виде специальных именованных блоков, названных
макроопределениями, а затем многоразово использовать имя
макроопределения для макровызова в макрокоманде, обеспечивающей воспроизведение
модифицированных вариантов блока. Макроопределение само по себе, как и
структурный блок, рассмотренный в предыдущей главе, не порождает никаких
машинных кодов. Однако, если при обработке структурного блока делается
разметка относительных адресов его элементов и их закрепление за их именами, то
в макроопределении подобные действия для меток отсутствуют. В процессе ас-
Язык Ассемблера в программировании информационных и управляющих систем QQ
семблирования транслятор MASM автоматически генерирует по каждому
отдельному макровызову последовательность предложений в соответствии с
макроопределением. При генерации макрорасширения используется правила
контекстной замены аргументов, после чего полученное макрорасширение
обрабатывается на равных правах с другими операторами Ассемблера.
Макроопределение должно предшествовать первой макрокоманде,
использующей его, и может быть включено как непосредственно в текст
программы, так и подключено из другого файла с помощью директивы:
INCLUDE имя файла
В программе на Ассемблере макрокоманды вообще выполняют те же
функции, что и процедуры, то есть обеспечивают выполнение функционально
завершенного действия с параметричным управлением и имеют следующие
особенности:
• процедура определяется в программе один раз, в то время как
макрорасширение генерируется заново для каждой макрокоманды на этапе
трансляции;
• текст процедуры неизменен, или может быть изменен только на шаге
выполнения, что не всегда желательно или допустимо, а содержание
макрорасширения зависит от параметров макрокоманды.
Формальные параметры макроопределения являются внутренними
именами макроопределения. Количество параметров не ограничено, но все они
должны помещаться в одной строке через запятую. Фактическими
параметрами могут быть символьные имена константы, мнемоники регистров, команд,
метки, переменные, используемые в программе. Параметры макросредств
являются позиционными; фактические параметры заменяют формальные при
генерировании макрорасширения. Поскольку макроопределение не порождает
машинные коды, все адреса в макрорасширении зависят от месторасположения
макрокоманды в программе. Количество макрорасширений равно количеству
макрокоманд. Большинство версий макросредств в Ассемблерах допускают
вложенность вызовов и ограниченную рекурсивность при условии использования
директив условной трансляции.
В макроопределениях могут использоваться специальные операторы
Ассемблера:
<текст> - текстовый оператор литерала позволяет рассматривать текст
на следующем уровне генерации как неделимый атомарный объект, включая
ограничители, и использовать его как фактический параметр;
\символ - символьный оператор литерала, позволяет использовать
следующий ограничитель как фактический параметр;
%текст - оператор выражения трактует следующий текст перед
макрогенерацией как выражение, значение которого замещает формальный
параметр.
;; текст - макрокомментарий, не отображаемый в макрорасширении.
I 00 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
& формальный параметр - оператор присоединения параметра к строке
символов, не являющимися ограничителями; символ & может использоваться
как перед, так и после имени формального параметра для выделения параметра
в строке символов.
Во вложенных макроопределениях оператор замены может быть
использован для задержки замены формального параметра фактическим значением,
при этом символ & указывается несколько раз. MASM трактует количество
символов & как глубину вложенности макроопределений относительно
текущего уровня, на котором нужна замена формального параметра. В
макроопределениях используются директивы общего назначения MACRO, LOCAL,
EXITM. Макроопределение представляет собой блок, определяющий
уникальное имя макро, который начинается директивой MACRO и заканчивается
директивой ENDM:
имя макро MACRO формальные параметры
LOCAL список имен ; Локализация имен макро
инструкции и директивы или тело макроопределения
ENDM
Поскольку каждое макроопределение может использоваться
многократно, для предотвращения возникновения повторных определений директива
LOCAL трансформирует метки и имена, специфицированные в нем, к виду:
Иномер, где общий порядковый номер локального имени в программе
представляется шестнадцатиричным числом от 0000 до FFFF. Директива
EXITM обеспечивает выход из генерации текущего макрорасширения до
достижения директивы ENDM.
Простейшие примеры макроопределений можно составить для
выполнения любого действия, требующего порождения кодов для обработки данных
разных типов при использовании близкого текста программы. Отсюда следует
возможность использования двух важнейших типов макросов: макросы для
генерации разнообразных вариантов процедур и фрагментов программ и
макросы для генерации вызывающих последовательностей. Например, тело
процедуры суммирования многобайтных данных может быть построено на основе
одно- и двухбайтных операций и может быть задано макроопределением:
mbacld macro t, tr, n, dst, src
local 10
Прототип со списком формальных аргументов :
t - буквенное обозначение типа данных (Ь или w)
tr - буквенное обозначение типа регистра (1 или х)
п - количество элементов
dst и src - многобайтные аргументы: приемник и источник
LES Dl/dst ; Загрузка указателя первого операнда
ЬЬВ SI,src ; Загрузка указателя второго операнда
MOV СХ,п ; Загрузка количества элементов
CLC ; Предварительная очистка бита переноса
10:LODS&t ; Загрузка элемента первого операнда
Язык Ассемблера в программировании информационных и управляющих систем 1 О I
XCHG AX,DX
LODS&t ; Загрузка элемента второго операнда
ADC A&tr,D&tr ; Сложение с распространением переноса
STOS&t ; Сохранение очередного элемента результата
LOOP 10 ; Циклическое повторение сложений
endm
С помощью этого макроопределения можно получать фрагменты
программы выполняющие многобайтное суммирование в двух режимах: либо по
байтам, либо по словам, используя разные макровызовы, например:
mbadd b,I,5,BinDst,BinSrc ; сложение двух 5-байтных данных
mbadd w,х,4,WinDst,WinSrc ; сложение двух 8-байтных данных
Приведенные в качестве примера макрокоманды или макровызовы
инициируют работу макрогенератора, который порождает операторы
макрорасширения, обрабатываемые в дальнейшем Ассемблером.
Для корректной обработки этих макрокоманд в одной программе
необходимо использование директивы LOCAL, чтобы предотвратить ошибки
повторного описания метки 10. Однако на практике, такие макроопределения
чаще всего разбиваются на два макроопределения:
• для вызывающей по следов ательсти, включающей три первые команды
пересылки и команду CALL с именем вызываемой процедуры;
• для тела подпрограммы, включающей остальные команды с
добавленной командой RET для выхода из процедуры.
Возможности макросредств значительно усиливаются блоками
повторений и условной трансляции, которые могут использоваться и вне макросредств.
Блок повторения представляет собой макрообъект, выполняющий
многократное дублирование тела блока с незначительными изменениями текста любых
инструкций языка. Блоки повторения начинаются директивами REPT, IRP и
IRPC и имеют формат:
Начальная инструкция
Директивы или тело блока
ENDM
Заранее определенное количество повторений тела блока задается
начальной инструкцией:
REPT выражение}
Пример применения такого блока мы уже рассматривали при передаче
аргументов в стандартные подпрограммы в предыдущем разделе. Выражение в
этой директиве определяет количество неизменяемых повторений блока.
Повторение блока операторов с разными значениями аргументов в
периоде компиляции задается заголовком блока:
I 02 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
IRP имя параметра, <список значений лараметров>
Тело повторяется для каждого значения параметра из списка, при этом
имя параметра в тексте блока поочередно заменяется значениями из списка.
Сгенерированный таким образом текст в дальнейшем обрабатывается
ассемблером.
IRPC имя параметра, строка символов
При этом текст повторяется для каждого символа и буквы строки, и
каждый раз имя параметра в тексте замещается очередным символом строки. В
процессе генерации блоков повторений, кроме системных параметров,
задаваемых в команде, удобно применять и пользовательские параметры, задаваемые
именами пользователя с использованием значений, изменяемых в процессе
трансляции, например, для подсчета порядкового номера элемента списка.
Такая внутренняя переменная пользователя в Ассемблере задается директивой:
имя переменой ~ выражение
Эту директиву нельзя путать с директивой: имя EQU выражение,
которое задает для имени фиксированные значения, неизменяемые в процессе
трансляции. Директива s = s+1 наращивает на единицу значение s на этапе
трансляции и при включении в блок повторения будет подсчитывать номер
генераторного цикла. Важно отметить, что такие команды можно использовать не
только в макроопределениях, но и в любых ассемблерных текстах.
5.2. Управление трансляцией в Ассемблере MASM
Директивы условного ассемблирования обеспечивают управление
процессом ассемблирования путем подключения или отключения фрагментов
исходного текста на Ассемблере. Эти директивы также могут использоваться в
любом месте программы, включая макроопределения. Наибольший эффект они
могут приносить именно в макроопределениях.
В общем виде блок условного ассемблирования имеет вид:
директива условного ассемблирования IFx
инструкции и директива, или тело блока
[ELSE
инструкции и директивы, или тело блока]
ENDIF
Директива условного ассемблирования определяет условие, при
котором будут ассемблироваться предложения блока, расположенные
непосредственно после директивы IF до директивы ELSE, или ENDIF. В условиях
разрешаются только те выражения, аргументы которых преобразуются в константы
при ассемблировании. Если условие не выполняется, ассемблируется группа
Язык Ассемблера в программировании информационных и управляющих систем 1 03
предложений, расположенная между ключевыми словами ELSE и ENDIF.
Допускается вложение таких блоков с предельной глубиной 255. В принципе все
условные блоки однотипны и их выполнение определяется исключительно
первым оператором. При использовании директивы
IF выражение
блок ассемблируется при ненулевом значении выражения, а - директивы,
IFE выражение
наоборот. Директивы IFl, IF2 не используют операнда и проверяют номер
просмотра ассемблера и обеспечивают выполнение условия только на
соответствующем проходе транслятора. Директивы
IFDEF имя
и
IFNDEF имя
проверяют определено ли в программе указанное в директиве имя.
IFDEF обеспечивает ассемблирование, если имя определено, a IFNDEF -
наоборот. Директивы
IFB параметр
и
IFNB параметр
проверяют значение аргумента (имя или число), и блок ассемблируется, если
аргумент является пробелом (IFB), или наоборот (IFNB). Чаще всего эти
директивы используются в макроопределениях для анализа его параметров.
Директивы
IFIDN <строка-1>, <строка-2>;
IFDIF <строка-1>,<строка~2>
сравнивают специфицированные аргументы, как символьные строки, и
вызывают ассемблирование блока при их идентичности (IFIDN), или наоборот
(IFDIF).
Обобщим рассмотренное выше макроопределение для построения
процедуры десятичного многобайтного сложения
mgpadd macro t,tr
local 10
; t - буквенное обозначение типа данных (b, w или d)
CLC ; Предварительная очистка бита переноса
10:LOD3&t ; Загрузка элемента первого операнда
1 04 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
XCHG AX,DX
LODS&t ; Загрузка элемента второго операнда
ADC A&tr,D&tr ; Сложение с распространением переноса
ifidn t,<d> ; Проверка десятичного типа данных
DAA ; Десятичная настройка результата сложения
endif
STOS&t ; Сохранение очередного элемента результата
LOOP 10 ; Циклическое повторение сложений
RET
endm
Такое макроопределение будет генерировать корректную процедуру для
макрокоманды
mgpadd d,I
Однако значение второго аргумента х будет несовместимо со
значениями первого аргумента b или d. В связи с этим разработчик макроопределения,
понимая нежелательность такой ситуации, должен проинформировать
пользователя о возникшем противоречии директивой .ERR сообщение.
Эта директива выдает пользователю сообщение об ошибке и
соответствующим образом влияет на формирование выходного кода состояния
транслятора с Ассемблера. Для оформления рассмотренной ситуации в Ассемблере
MASM разработчик должен составить довольно сложный условный блок:
ifidn tr,<l>
ifdif t,<b>
ifdif t,<d>
.ERR x> Недопустимый тип аргументов"
endif
endif
else
ifidn tr,<x>
ifdif t,<w>
.ERR "Недопустимый тип аргументов"
endif
endif
endif
и вставить его практически в любое место программы. В Turbo Ассемблере
этот блок может быть упрощен благодаря использованию команд
формирования условных ошибок.
5.3. Сложные выражения в Ассемблере и их использование
В принципе любой операнд машинной команды или директивы
Ассемблера может быть представлен достаточно сложным выражением. Транслятор с
Язык Ассемблера в программировании информационных и управляющих систем 1 05
Ассемблера всегда проверяет соответствие синтаксического типа выражения
семантике операнда, определенной синтаксическими правилами языка. Это
позволяет рассматривать выражения в Ассемблере в самом обобщенном виде.
Среди операторов выражений Ассемблера мы уже рассматривали такие как:
• ключевые слова типов, сопровождаемые в выражениях ключевым
словом PTR или THIS;
• ключевые слова размеров: LENGHT, SIZE, WIDTH, MASK;
• ключевые слова элементов адресов: OFFSET, SEG, HIGH, LOW.
Кроме них в выражениях Ассемблера возможно применение широкого
комплекса арифметических и логических операторов:
• арифметические операторы выражений, выполняющие унарные
операции "+" или "-", двуместные операторы "+", "-", и*", 7", mod с
традиционной семантикой и приоритетами;
• операторы арифметических отношений: EQ - равенство, пе -
неравенство, GT - больше, GE - меньше или равно, LE -меньше или равно, LT -
меньше, с приоритетами меньшими, чем у арифметических операторах;
• операторы логических операций NOT - инверсия, AND - конъюнкция, OR -
дизъюнкция; XOR - исключающее ИЛИ (поразрядное сложение по модулю
2) и сдвиги SHL - влево; SHR - вправо, выполняемые в поразрядном
режиме.
Результаты операций отношения представляются нулем, если отношение
ложно, и "-1" или единицами во всех двоичных разрядах, если отношение
истинно. Логические и сдвиговые операции имеют неотличимые аналоги среди
машинных команд, но нужно помнить, что операции выражений выполняются
в процессе трансляции, а машинные команды - в процессе выполнения.
При задании операндов машинных команд нужно различать адресные,
регистровые и непосредственные операнды. В адресных операндах практически
никогда не применяются более сложные операции, чем сложения для
определения смещенных и относительных адресов. Для анализа правильности сложных
операндов удобно ввести понятие типа переместимости, задающего номер
сегмента, в котором определено имя или текущий адрес. Базовым типом
переместимости будем считать абсолютный тип, который соответствует константам.
Имена, определенные в переместимых сегментах, считаются переместимыми. К
переместимым именам можно только добавлять или вычитать абсолютные
смещения, а также вычитать переместимые имена из одного сегмента для
получения абсолютного результата. Остальные действия над переместимыми именами
считаются некорректными и приводят к выдаче диагностики транслятором. В
непосредственных операндах могут применяться сложные выражения, определяемые
логикой использования операндов.
В языке Ассемблера используется также ряд предопределенных имен,
таких как, например, имена регистров АХ. CS и т д., и специальный
объект-указатель текущего адреса, задаваемый знаком *$' . Этот указатель имеет значение
относительного смещения в текущем сегменте перед обработкой
рассматриваемой команды или директивы.
I 06 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
Отношения и логические операторы целесообразно использовать только
при проверке условий в разнообразных разновидностях директив условной
трансляции, прежде всего, IF и IFE.
В том случае, когда часто приходится использовать в программе
сложные значения для одного и того же операнда в разных командах удобно
использовать директиву EQU для создания имени сложного операнда, например:
parl EQU Dword ptr[BP+4]
может быть использован для определения операнда в стеке параметров с типом
двойное слово. При этом важно отметить, что выражение в директиве EQU
может иметь практически любую сложность и любое содержание.
5.4. Управление режимами трансляции в Ассемблере MASM
Управление трансляцией в Ассемблере MASM разбито по трем
направлениям:
• управление целевыми процессорами директивами, задаваемыми в одной
строке .8086, .8087, .186, .286, .286р, .287, .386, 386р, .387, .486, .486р и
т.д., из названия которых, с учетом того, что буква р определяет
защищенный режим, явно следует о каком процессоре и режиме идет речь;
• управление моделями памяти и заданием сегментных регистров;
• управление выдачей листинга и объектных кодов.
Процессор 8086, а также все последующие модели, образующие
семейство процессоров ix86, имеют как минимум четыре сегментных регистра - CS,
DS, ES и SS. Эти регистры содержат адрес сегмента, позволяющий процессору
в реальном режиме обращаться к физическому блоку памяти размером до 64К.
Любой адрес машинной команды вычисляется с использованием одного из
этих регистров в качестве значения начального адреса сегмента.
В большинстве случаев программы для процессоров ix86 состоят из
нескольких сегментов, где каждый сегмент физически является частью кода
программы или данных. Доступ к тому или иному сегменту можно обеспечить,
используя сегментные регистры. Основываясь на этой схеме, получено несколько
различных моделей организации программы. Из таких соображений
разработано несколько стандартных моделей организации памяти, рассмотренных
предварительно в разделе, посвященном модульному программированию.
Поскольку большинство языков программирования следуют этим соглашениям, в
языке Ассемблера тоже стараются придерживаться их.
Базовый подход к разбивке программы на модули состоит в отделении
команд программы от ее данных. При этом можно классифицировать каждую
часть данных программы как инициализированные данные (например,
текстовые сообщения и начальные численные значения), или неинициализированные
данные, о создании которых может заботиться программист, но в последних
версиях трансляторов предусмотрена возможность автоматизированного
распределения некоторых видов данных по сегментам.
Язык Ассемблера в программировании информационных и управляющих систем I О7
Директива .MODEL позволяет задавать в программе одну из
нескольких стандартных моделей сегментации. Кроме того, ее можно использовать для
указания языка, с которым выполняется сопряжение в процедурах программы.
Синтаксис директивы .MODEL:
MODEL [модификатор] модель_па.мяти [имя_кодового__сегм&нта]
Модель_памяти и модификатор созадают модель сегментации памяти
для использования программой.
Стандартные модели памяти, доступные трансляторам с Ассемблера,
имеют специальные сегменты: для кода программы, инициализированных
данных, неинициализированных данных, инициализированных данных дальнего
типа, неинициализированных данных дальнего типа, констант, стека.
Кодовый сегмент обычно содержит код программного модуля (хотя он
может содержать и данные - в защищенном режиме только константы).
Инициализированные данные и константы рассматриваются отдельно только для
совместимости с некоторыми языками высокого уровня. Они содержат данные,
подобные сообщениям, где заданные значения используются для выполнения
программы. Неинициализированные данные и стек содержат данные,
начальное значение которых несущественно для выполнения программы.
Инициализированные данные дальнего типа это данные, которые не содержатся в
стандартном сегменте данных и получены только изменением значения сегментного
регистра. Модуль может содержать более одного сегмента данных такого типа.
Неинициализированные данные дальнего типа подобны инициализированным
с той лишь разницей, что их содержимое в момент загрузки программы не
имеет определенных значений и они чаще всего сохраняют предыдущее
содержимое или заполняются нулями.
Указание модели памяти определяет, как выполняются ссылки к этим
сегментам памяти с применением сегментных регистров и как они
объединяются в группы. При написании программы все эти сегменты оформляют отдельно,
не обращая внимания на размер программы. Затем, по заданной требуемой
модели памяти, их объединяют в единый физический сегмент или группу. При
сохранении сегментов в отдельности и последующем увеличении размеров
программы необходимо выбирать большую модель памяти.
Директива .MODEL для Ассемблера MASM и Turbo Ассемблера имеет
только один обязательный параметр - модель памяти, который может
принимать одно из следующих значений:
Модель
TINY
SMALL
MEDIUM
COMPACT
LARGE
Код
near
near
far
near
far
Данные
near
near
near
far
far
Предпологаемые значения регистров
S=DGROUP DS=SS=DGROUP
CS= TEXT DS=SS=DGROUP
С8=<имя_модуля>_ТЕХТ
DS=SS=DGROUP
CS= TEXT DS=SS=DGROUP
С8=<имя_модуля>_ТЕХТ DS=SS=DROUP
I 08 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
Модель
HUGE
TCHUGE
TPASCAL
FLAT
Код
far
far
near
near
Данные
far
far (T)
far (T)
near
Предполагаемые значения регистров
С8=<имя модуля>_ТЕХТ
DS=SS=DGROUP
Б8=<имя модуля> TEXT DS=NOTHING
SS^NOTHING
CS=CODE DS=DATA SS=NOTHING
CS=_TEXT DS=SS=FLAT
Модели помеченные знаком (Т) допустимы только для Turbo
Ассемблера. Каждая модель памяти предназначена для какого-то типа организации
выполняемого файла и как можно убедиться в основном соответствует
соглашениям, принятым в компиляторах Microsoft С, приведенных выше.
Для автоматизированного распределения кодов и данных сегментам с
присваиванием этим сегментам стандартных имен перед соответствующими
блоками после общей заглавной директивы .MODEL задаются директивы:
.DAT&
Директивы определения данных
. CGtJST
Директивы определения данных
.CODE
Машинные команды и процедуры
.STACK
Директивы резервирования памяти
При этом генерируются сегменты, совместимые с большинством языков
программирования.
Стек обычно содержит достаточно большое количество
неинициализированных данных. Кроме того, информация о начальной загрузке указателя
стека передается загрузчику и при запуске программы регистры SS и SP обычно
устанавливаются автоматически на область стека. Поэтому стандартные
модели памяти рассматривают стек как отдельный сегмент.
В программах на Ассемблере сегменты можно объединять в группы или
укрупненные физические сегменты. Преимущество применения групп
заключается в том, что для всех сегментов группы можно использовать одно и то же
значение сегментного адреса. Например, инициализированные данные,
неинициализированные данные и сегмент стека в малых моделях объединяются в
группу с одним и тем же значением сегмента, используемым для доступа ко
всем данным программы.
5.5. Особенности управления трансляцией
в Ассемблере TASM
Turbo Ассемблер имеет режим полностью совместимый с MASM,
расширенный разнообразными возможностями модульного программирования,
Язык Ассемблера в программировании информационных и управляющих систем 1 09
включая и объектно-ориентированные и способы задания параметров и
внутренних данных при оформлении процедур на Ассемблере. Все
усовершенствования Turbo Ассемблера направлены на включение в его состав практически
всех лингвистических возможностей языков высокого уровня, связанных с
организацией структур данных и программ.
В плане описания структур данных Turbo Ассемблер используя все
возможности Ассемблера MASM, имеет возможности для описания структур
данных, совместимых с программами языка С. Сюда относятся:
• данные перечислимого типа, определяемые директивой ENUM;
• объединения, построенные по той же схеме, что и структуры с заглавной
директивой UNION;
• вложенные комбинации структур и объединений;
• определения таблиц для спецификации и систематизации методов,
связанных с объектами с заглавными директивами TABLE и VIRTUAL;
• определение процедурных типов заглавной директивой PROCTYPE;
• определение объектов, через включение в структуры комплексов
связанных с объектами методов с заглавной директивой METHOD.
Объектно-ориентированные возможности будут подробнее рассмотрены
в предпоследнем разделе книги.
Операторы объявления и инициализации данных мало отличаются от
MASM, за исключением специфических новых типов данных, например
перечислимых, для которых используется схема близкая к языку С. Для работы с
объектами используются директивы статического создания экземпляров
таблиц и объектов. Кроме того, включен дополнительный тип данных,
совместимый с данными типа real в языке Pascal.
Turbo Ассемблер всегда помещает инициализированные данные в
отдельный сегмент, что позволяет разместить его в конце программы, уменьшая
тем самым размер выполняемого файла. В специфическом режиме Turbo
Ассемблера, называемом IDEAL, применяются те же директивы, что и MASM, но
без ведущей точки. Кроме того, во всех директивах описания ключевое слово
директивы задается на первом месте, имя создаваемой структуры, процедуры или
объекта - на втором.
Макросредства Turbo Ассемблера расширены возможностями
использования специальных директив генераторных циклов
WHILE выражение
операторы
ENDM
и безусловных генераторных переходов
GOTO метка}
с помощью которых выдача информации об ошибке макровызова
многобайтного суммирования может быть представлена так:
110 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
ifidn tr,<l>
ifidn t,<b>
GOTO le
else
ifidn t,<d>
GOTO le
endif
endif
else
ifidn tr,<x>
ifidn t,<w>
GOTO le
endif
endif
endif
.ERR "Недопустимый тип аргументов"
le:
При объявлении режимов Turbo Ассемблера необязательный параметр
модификатор в объявлении директивы MODEL позволяет изменять некоторые
черты указанной модели, причем при необходимости можно задавать
несколько модификаторов.
Параметр-модификатор языка указывает на необходимость добавить в
код процедур специальные прологи и эпилоги при разработке программ для
WINDOWS или Borland Overlay Loader. Значениями этого параметра могут
быть NORMAL, WINDOWS, ODDNEAR и ODDFAR. По умолчанию
устанавливается NORMAL.
Для совместимости с MASM 5.2 предусмотрено использование
модификатора в двух местах директивы MODEL. По умолчанию Turbo Ассемблер
использует модификаторы NEARSTACK, DOS и USE 16 (USE32, если выбран
процессор 80386 или 80486).
Параметр имя_кодового_сегмепта, который также является
необязательным, может использоваться в модели LARGE и ей подобным для
переопределения имени, назначенного кодовому сегменту по умолчанию (обычно это имя
модуля с добавлением к нему TEXT).
Параметры языка и модификатор языка определяют соглашения для
вызова процедур, а также стиль пролога и эпилога процедуры, принятый по
умолчанию. Кроме того, они определяют, как представляются внешние имена для
использования их компоновщиком. Turbo Ассемблер автоматически
генерирует код входа и выхода из процедур в стиле одного из следующих языковых
соглашений: PASCAL, С, СРР (C++), SYSCALL, STDCALL, BASIC, FORTRAN,
PROLOG и NOLANGUAGE. Если язык не указан, Turbo Ассемблер
использует NOLANGUAGE.
Изменять языковые соглашения, принятые по умолчанию, можно не
только в директиве MODEL, но и при объявлении процедур и
идентификаторов. Материал, посвященный этим темам, излагается в последующих главах.
Язык Ассемблера в программировании информационных и управляющих систем 111
При использовании директивы MODEL Turbo Ассемблер создает и
инициализирует несколько идентификаторов для отражения деталей текущей
модели памяти. Эти переменные вместе с операторами условной трансляции
используют для написания программ, независящих от модели памяти. Идентификатор
@MODEL представляет для анализа или передачи в выходной объектный файл
действующую модель памяти. Он описан как текстовый макрос и принимает
одно из следующих значений:
l=TINY 5= LARGE
2= SMALL, FLAT 6= HUGE
3= COMPACT 1= TCHUGE
4= MEDIUM 8= TPASCAL
Тип используемых сегментов, установленный для текущей модели
памяти, находится в предопределенном идентификаторе @32Bit. Если директивой
MODEL установлен размер сегмента 16 бит, идентификатор @Bit приобретает
значение 0, если 32 бита - значение 1.
Идентификатор @CodeSize указывает на размер, установленный по
умолчанию для указателя программного кода и определяемый текущей
моделью. Если в модели памяти используются указатели типа NEAR (TINY,
SMALL, FLAT, COMPACT, TPASCAL), этот идентификатор приобретает
значение 0, а если типа FAR (все остальные модели памяти) - значение 1.
Идентификатор @DataSize указывает на размер, установленный по
умолчанию для указателя данных программы и определяемый текущей
моделью. Если в модели памяти используются указатели на данные типа NEAR
(TINY, SMALL, FLAT, MEDIUM), этот идентификатор приобретает значение
О, если типа FAR (COMPACT, LARGE, TPASCAL) - значение 1. Для моделей
памяти HUGE и TCHUGE @Datasize получает значение 2.
Идентификатором ©Interface обозначается информация о языке и
операционной системе, выбранных директивой MODEL. Этот предопределенный
идентификатор содержит число, биты которого имеют следующие значения:
Биты 0-6
0000000
0000001
0000010
0000011
0000100
0000101
0000110
0000111
0001000
Значение
NOLANGUAGE
С
SYSCALL
STDCALL
PASCAL
FORTRAN
BASIC
PROLOG
CPP
Бит 7 может принимать значение 0 для DOS и 1 для OS/2. Например,
значение 81h, представляемое идентификатором @Interfase, свидетельствует о
выборе операционной системы OS/2 и языка С. Рассмотренные обозначения га-
112 Глава 5 Особенности синтаксиса основных версий языка Ассемблера
рантированы только для языковых реализаций фирмы Borland и совместимы с
большинством реализаций фирмы Microsoft.
Краткие итоги
Материалы этой главы представляют возможности базовых версий
Ассемблера и позволяют выбирать подходящую версию в зависимости от
потребностей программиста.
ГЛАВА 6
ИСПОЛЬЗОВАНИЕ СИСТЕМЫ ПРЕРЫВАНИЙ И
ПРОГРАММИРОВАНИЕ ВВОДА-ВЫВОДА
В этой главе рассмотрены осабеншхпт программирования
ввода-вывода на уровне обращений к операционным системам и
организация обработки программных и аппаратных прерываний,
включая прерывания ос обых с чу чаев
6.1. Понятие прерываний и их реализация
в современных процессорах
Прерывания (в английском языке interruption) получили свое название
по их основной функции - приостановить процесс основных вычислений в
центральном процессоре для выполнения вспомогательных, технологических и
сервисных действий ОС. Процедуры обработки прерываний, которые могут
рассматриваться как подпрограммы с обязательным обращением через
косвенный межсегментный адрес, размещенный в фиксированной области памяти
составляют особый класс подпрограмм. Прерывания принято классифицировать
по трем типам: программные, аппаратные и отладочные.
Это дает программам обработки прерываний уникальную возможность
быть вызванными из любой программы, размещенной в памяти, с помощью
команды INT vet. В реальном она передает управление по адресу, записанному в
одном из 256 4-байтных векторов, размещенных последовательно в первом
килобайте памяти процессора ix86. Все прерывания в Ассемблере
программируются как специальные процедуры с возвратом в прерванную задачу командой
IRET. Обращение к встроенным функциям DOS или BIOS выполняется
стандартным образом, при этом для передачи управления чаще всего используется
команда INT. Более того, множество системных программ используют
разнообразные прерывания для выполнения своих функций. Даже сравнительно
краткий перечень основных прерываний с минимальным описанием их
синтаксиса и семантики их выполнения занимает объемистый двухтомник [11].
Программы обработки прерываний используются одним из двух
возможных путей: через программное обращение для внутренних прерываний и
через аппаратный вызов для внешних прерываний. Использование векторов
прерываний определяется особенностями резервирования начальных уровней
прерываний для обработки особых ситуаций в ВК. а также структурой и
программированием оборудования обработки аппаратных прерываний.
Архитектурой процессоров ix86 регламентировано использование следующих векторов
прерываний.
О - некорректность целочисленного деления: деление на 0 или на малый
делитель, переполняющий регистр-приемник результата в моделях, начиная с
114 Глава 6 Использование системы прерываний и программирование ввода-вывода
i286, адрес возврата указывает на начало некорректной команды, что
облегчает ее повторное выполнение с откорректированными операндами.
1 - переход в режим отладки для пошагового режима при tf^l и для
режимов отладки по аппаратным регистрам i386 и выше с разными вариантами
ловушек, заданных в регистрах отладки.
Прерывания векторов 0 и 1 переходят на обработку внешних
аппаратных прерываний, если такие имеются, сразу после запоминания адреса
возврата и сброса tf=O.
2 - обработка немаскированного аппаратного прерывания, которое
было разработано для обслуживания потенциального отказа питания, но чаще
используется для других целей:
• обработка ошибок памяти;
• служба времени, временной сторож и прерывания по тайм-ауту.
Начиная с i286 возвращается к началу прерванной команды (прерванная
команда не заканчивается до входа в прерывание).
3 - приостановка программы в процессе отладки. Однобайтный вариант
команды используется в отладчиках реального режима для подмены байта
кода в позиции точки останова.
4 - переход на обработку обобщенных случаев переполнений.
5 - обработка выхода за границы массивов при выполнении команды
BOUND для процессоров И 86 и выше, после чего возврат выполняется на
повторение этой команды.
6 - обработка недопустимого кода операции, к которой относят
обращения к отсутствующим в системе команд операциям или попытку выполнения
привилегированных команд защищенного режима при недостаточном уровне
привилегий или в реальном режиме. Может использоваться для эмуляции
исключительных команд, таких как недокументированная LOAD ALL в BIOS систем
на процессоре i386.
7 - обработка команд сопроцессора при его отсутствии. Может
использоваться для программной эмуляции математического сопроцессора.
8 - обработка двойного исключения (exeption), свидетельствующего о
наличии нескольких особых ситуаций при обработке одной команды, которая
начинается в случае возникновения двух исключений в одной команде или
возникновения новых исключений в обработчике исключений. Если такая
ситуация возникает в обработчике двойного исключения, то ЦП переходит в режим
SHUTDOWN, который аппаратными цепями PC/AT преобразуется в
аппаратный сброс и обеспечивает наиболее быстрый возврат в реальный режим.
9 - прерывание, зарезервированное фирмой Intel для обработки отказа
защиты при операциях с плавающей точкой в процессоре i486, но практически
неиспользуемое.
Oah - ошибочный сегмент таблицы задач (TSS) по внутренним
характеристикам и уровням привилегий.
Obh - отсутствие нужного сегмента памяти по признаку наличия
сегмента. Может использоваться для организации виртуальной памяти путем
перегрузки сегментов.
Язык Ассемблера в программировании информационных и управляющих систем I 1 О
Och - выход за пределы выделенного стека, в основном контролирует в
защищенном режиме переход границ стека и при чтении и наличие
фактического сегмента в памяти.
Odh - защита по предельным размерам смещений и команд,
проверяющая переход границ сегмента, обращение к сегменту с неразрешенным методом
и правами доступа и работу с неправильными селекторами сегментов в
защищенном режиме.
Oeh - отсутствие страницы памяти, которое возникает при попытке
обращения к области памяти, отсутствующей в селекторных таблицах.
10h - ошибка сопроцессора, фиксируемая в ЦП при формировании на
выходе сопроцессора сигнала ERROR.
При этом следует отметить, что в процессоре i86 регламентировано
использование только первых 5 уровней прерываний, а архитектура IBM/AT
предусматривает использование уровней 8 - Ofh для подключения аппаратных
прерываний, что приводит к конфликтам и вытекающей из них необходимости в
дополнительных действиях для распознавания причины прерывания.
Так, например вектор прерываний Odh в компьютерах с шиной,
совместимой с AT и ее дальнейшими модификациями, используется для обработки
аппаратного прерывания по требованию запроса от порта принтера LPT2. Это
позволяет различить какое из двух прерываний (особый случай ЦП или запрос
обслуживания внешнего устройства) произошло, проанализировав либо
состояние схем управления аппаратными прерываниями, либо состояние
регистров защищенного режима, что гораздо сложнее выполнить. Конфликты
могут возникать и между любыми другими сочетаниями прерываний, включая
программные. Преодоление конфликтов между разными программами,
размещенными по одним векторам прерываний является серьезной проблемой
разработчиков системных управляющих программ. Почти такую же сложную задачу
состовляет анализ точной причины прерывания, вызванного аппаратурой
процессора.
При обработке особых случаев по данным и адресам необходимо
скомпенсировать результаты ошибки, сделать возможными дальнейшие вычисления
по программе. Например, при возникновении особого случая деления
необходимо проинтерпретировать команду деления, адрес которой доступен через
адрес прерванной команды зафиксированной в стеке, предварительно проверив
допустимость аргументов и приняв решение о нейтрализации последствий
особого случая и возможности формирования результата. Доступ к команде
можно осуществить такой последовательностью команд:
push bp ; Сохранение указателя старой верхушки
mov bp,sp ; Получение доступа к верхушке стека
push es ; Сохранение значения es для прерванной программы
les di,[bp+4] ; Загрузка в es:di указателя на команду
; деления
I I 6 Глава 6 Использование системы прерываний и программирование ввода-вывода
После этого можно проанализировать операнды, если необходимо -
исправить их и выполнить команду в отладочном режиме или вернуться на
повторное выполнение команды.
Такой подход снимает необходимость предварительной проверки
допустимости команд и их адресов для обеспечения постоянного режима корректной
работы, который приводит к существенному снижению скорости выполнения
программ. Распознавание команды, вызвавшей особый случай, также можно
рассматривать как элемент распознавания ситуации в случае конфликтных
прерываний.
Обработка прерывания в IBM PC связана с выполнением машинной
команды INT N, где N - номер вектора прерывания. По способу выборки
процессором команды INT N с шины данных (или опосредовано через очередь
команд) прерывания подразделяются на:
а) программные (внутренние) - команда INT N включена
программистом в естественную последовательность команд, т. е. находится внутри
программы;
б) аппаратные (внешние) - команда INT N "вставляется" в естественную
последовательность машинных инструкций аппаратными средствами
контроллера прерываний, инициализируемого внешним сигналом запроса прерывания.
Внешнее прерывание создает произвольное место разрыва
последовательности команд и вставки контроллера приоритетных прерываний машинной
команды INT N.
В любом случае процессор приостанавливает решение задачи,
выполняет прерывание, а затем возвращается на прежнее место. Для того чтобы иметь
возможность вернуться точно в нужное место программы, адрес этого места
(CS:IP) запоминается в стеке, вместе с регистром флагов. Затем в CS:IP
загружается адрес программы обработки прерывания и ей передается управление.
Программы обработки прерываний иногда называют обработчиками или
драйверами прерываний (handler). Они всегда завершаются командой IRET
(возврат из прерывания), которая заканчивает процесс, начатый прерыванием,
возвращая старые значения CS:IP и регистра флагов, тем самым давая
программе возможность продолжить выполнение из того же состояния.
6.2. Программные прерывания и их использование в
информационных и управляющих системах
Чаще всего пронумерованные функции системных программ ОС и их
расширений реализуются как функции специально выделенных векторов
прерываний с передачей параметров и аргументов через регистры и специальные
системные области памяти. Этот метод имеет преимущество в устранении
необходимости настройки адресов при вызове функций в перемещаемых
программах. Фактическая настройка адресов выполняется при загрузке резидентных
программ обработки прерываний.
Прерываниями обычно называют готовые процедуры обработчиков
прерываний, которые компьютер вызывает для выполнения определенной
задачи. Программные прерывания по существу ничего не прерывают. Это обыч-
Язык Ассемблера в программировании информационных и управляющих систем
117
ные процедуры, вызываемые программами пользователя для выполнения
рутинной работы, такой как прием нажатия клавиши на клавиатуре или вывод на
экран. Однако эти подпрограммы содержатся не внутри программы
пользователя, а в операционной системе и механизм прерываний дает вам возможность
обратиться к ним. Программные прерывания могут вызываться друг из друга.
Адреса программ прерываний называют векторами. Каждый вектор
реального режима имеет длину четыре байта. В первом слове хранится значение
IP, а во втором - CS. Младшие 1024 байта памяти содержат векторы
прерываний, таким образом, имеется место для 256 векторов. Вместе взятые они
называются таблицей векторов. Вектор для прерывания 0 начинается с ячейки
0000:0000, прерывания I - с 0000:0004, 2-е 0000:0008 и т.д. В защищенном
режиме информация о векторах прерываний помещается в таблице
дескрипторов прерываний IDT.
Типы базовых резидентных программ выбираются исходя из свойств
ОС - комплекса программ, обеспечивающих автоматическую обработку
директив оператора и автоматическое выполнение пользовательских и системных
программ. Наиболее простыми в IBM-совместимых персональных
компьютерах являются программные комплексы типа дисковой операционной системы
(DOS). Они включают средства запуска на выполнение единственной задачи,
средства обслуживания таких базовых устройств персональных компьютеров,
как клавиатура, дисплей, последовательные и параллельные системные порты
обмена, манипуляторы типа "мышь" и файловые системы для долговременного
хранения информации. Кроме того, ряд функций DOS обеспечивают
динамическое управление распределением свободной оперативной памяти и
обслуживанием подсистемы времени и звуковых устройств.
Среди наиболее распространенных DOS отметим MS DOS фирмы
Microsoft, DR DOS фирмы Digital Research и PC DOS фирмы IBM, которые с
точки зрения пользователей практически совместимы и построены на
одинаковых принципах внутренней организации. Они предназначены для эксплуатации
в однопрограммном режиме. Программный комплекс DOS использует
следующие векторы программных прерываний:
• 20h - завершение программы, адрес сегмента состояния которой
помещен в регистр CS;
• 21h - основные функции DOS;
• 22h - обеспечивает передачу управления по адресу PSP DWORD PTR [Oah];
• 23h - вызывается программами DOS при обнаружении Ctrl-C/Ctrl-Break;
• 24h - обработчики ошибок оборудования;
• 25h - чтение с дисков по абсолютным адресам;
• 26h - запись на диск по абсолютным адресам;
• 27h - завершить и оставить в резидентной памяти, считается
устаревшей, вместо нее рекомендуется функция 3lh прерывания 2lh;
• др. до 31h, зарезервированные для использования в DOS.
Среди этих функций часть (например, 22h, 23h и 24h) не предназначена
для пользовательских вызовов, а объем и семантика их параметров слишком
велики для сгрогой систематизации. Поэтому здесь мы остановимся только на
I IO Глава 6 Использование системы прерываний и программирование ввода-вывода
наиболее часто используемых функциях для изучения принципов их работы, а
по остальным отошлем читателя к справочникам и фирменной документации.
Большинство стандартных функций MS DOS реализованы как
пронумерованные функции прерывания INT 21 h, которые можно разделить на функции
информационного обмена и функции управления системными ресурсами.
Общие правила построения вызывающих последовательностей такие: через
регистр АН передают номер функции, регистры DS:DX сохраняют указатель
основного информационного объекта функции, a ES:DI - дополнительного объекта;
регистр СХ - передает атрибуты объекта функции, а ВХ - числовые
характеристики. Результаты чаще возвращаются в регистре AL или в бите CF в виде
признака ошибки.
Общие правила построения и модификации обработчиков прерываний
обусловлены необходимостью действий, распределенных в двух частях системной
программы:
• инсталляционная или подготовительная часть:
♦ размещение в системе в качестве резидентной программы,
♦ коррекция вектора прерываний;
♦ коррекция режимов работы программы при инсталляции и реин-
сталляции;
• эксплуатационная часть:
♦ распознавание или классификация фактического типа
наступившего прерывания;
♦ преодоление конфликтов с другими программами,
размещенными на том же уровне прерываний;
♦ выполнение семантической обработки или выдача сообщения о
ее невозможности.
Стандартные процедуры и функции ввода-вывода в языках
программирования высокого уровня обеспечивают программирование на так
называемом логическом уровне. Этот подход достаточно эффективен при работе с
главными стандартными устройствами и практически непригоден для
программирования оригинальных устройств с нестандартным интерфейсом или
управлением, а также для использования экзотических возможностей устройств.
Поэтому при продаже специфичных устройств фирмы-производители
сопровождают их специальным комплектом драйверов для организации
информационного обмена в программах, построенных для использования под разными ОС.
Программирование драйверов требует возможности вмешательства в
работу внешних устройств на физическом уровне. Для выполнения таких работ
в высокоуровневые языки программирования включают псевдопеременные,
псевдомассивы, специальные функции или специальные средства обращения к
машинным кодам типа ассемблерных вставок.
Программы информационного обмена традиционно строятся по
иерархическому принципу. По функциям пользователя различают функции логического
уровня, оперирующие с объектами программного уровня, и функции физического
уровня, оперирующие объектами данных в форматах внешнего оборудования. Дру-
Язык Ассемблера в программировании информационных и управляющих систем 1 1 Q
гая классификация иерархии программ обмена выполняется по прагматике их
использования:
• драйверы, непосредственно управляющие операциями обмена;
• программы блокирования-деблокирования, выполняющие
преобразования физических форматов записей в логические и обратные
преобразования;
• программы буферизации, обеспечивающие возможность параллельного
функционирования устройств обмена вместе с решением задач
центральным процессором.
С такой точки зрения базовыми программами обмена являются
драйверы. Другие программы исполняют вспомогательные функции и обеспечивают
взаимодействие с аппаратными прерываниями.
Простейшие драйверы ориентирован^ на выполнение простейших
функций ввода-вывода на физическом уровне. Более сложные, встраиваемые в
операционные системы, например в MS DOS, включают комплекс функций
полностью обслуживающий устройство, включая подготовительные операции
и десяток разновидностей обменов, включая необходимые позиционирования.
Практически при выполнении любой программы возникает
необходимость ввода или вывода информации, для чего в языках высокого уровня
имеются специальные операторы ввода-вывода, которые позволяют в удобной
форме реализовать эти функции. В системе команд ix86 также имеются
команды ввода-вывода, но они реализуют эти операции на самом низком
(физическом уровне), т.е. обеспечивают обращение к портам ввода-вывода по
конкретным адресам. Поэтому для обеспечения ввода или вывода информации на
этом уровне программист должен знать номера портов соответствующего
устройства, а также протоколы или алгоритмы обслуживания каждого
устройства. Все это требует не только подробного изучения системной документации,
но и высокого профессионализма. Для облегчения программирования
операций по вводу-выводу информации в операционной системе MS DOS
реализованы специальные сервисные функции, обеспечивающие ввод-вывод на
логическом уровне. При этом прикладная программа должна только сообщить
необходимые для данной функции параметры и передать управление DOS,
которая осуществляет все необходимые операции по управлению устройством и
передаче данных на физическом уровне, а затем возвращает управление
прикладной программе, сообщая также о том, успешно ли завершилась операция или
же были зафиксированы ошибки. Этот механизм в значительной степени
упрощает реализацию ввода-вывода, так как программисту необходимо только
знать схему вызова функций и требуемые параметры для ее выполнения.
Для понимания процессов ввода-вывода и механизмов их реализации
необходимо классифицировать внешние устройства по способам доступа.
Элементами доступа могут быть порты устройства или окна памяти, выделяемые
для взаимодействия с внешним устройством по данным. Порты устройств,
задаваемые физическими адресами ввода-вывода, нужно отличать от портов
компьютера или ВК, которые именуют полное устройство и его драйверы. С
I 20 Глава 6 Использование системы прерываний и программирование ввода-вывода
этих позиций методы доступа к устройствам можно классифицировать
следующим образом.
• Управление через порты программирования, команд и состояния с
использованием сигналов прерываний - обмен данными через порты
данных. По этому методу работают клавиатура, таймеры и устройства
управления звуком, система управления аппаратными прерываниями, а
также системные порты последовательной COMi и параллельной LPTi
передачи данных.
• Управление через командный порт и порт программирования - обмен
данными с центральным процессором через непосредственное окно
памяти; по этому методу работают видеосистемы и системы управления
верхней памятью.
• Управление через порты программирования и передача данных через
процессор прямого доступа к памяти (DMA - direct memory access). Этот
механизм обычно используют дисковые накопители.
Для реализации некоторых специфичных функций управление
соответствующими командами INT можно передавать непосредственно базовой
системе ввода-вывода (base input/output system - BIOS), программы которой
поставляются фирмами разработчиками аппаратуры. Причем части BIOS,
предназначенные для обслуживания системного блока и видеокарты поставляются в
форме ПЗУ на соответствующих платах. Например, через INT 10H реализуются
функции управления дисплеем, отсутствующие в наборе сервисных функций
DOS. Ниже поясняются особенности работы с файлами в ассемблерных
программах.
Файл представляет собой совокупность расположенных на диске в
логической последовательности записей, каждой из которых поставлен в
соответствие номер, причем нумерация ведется с нуля. Таким образом, указав имя файла
и номер записи, можно однозначно идентифицировать некоторую конкретную
запись, чтобы прочитать или записать ее. Имеется два способа доступа к
записям: последовательный и произвольный. В случае последовательного доступа
указатель чтения/записи файла устанавливается автоматически на следующую
позицию при выполнении каждой операции чтения или записи. При открытии
файла этот указатель устанавливается на начало файла. При произвольном
доступе необходимо перед операцией чтения (или записи) вначале произвести
позиционирование указателя чтения/записи файла на требуемую запись, а
затем осуществлять непосредственно операцию. Кроме функций чтения и записи
при работе с файлами требуются и некоторые другие функции, такие как
создание файла, если его еще нет на диске или открытие его для работы в программе,
если он ранее уже был создан. По окончании работы файл необходимо
закрыть. При выполнении операций чтения и записи в режиме прямого доступа
требуется также функция позиционирования указателя чтения/записи файла.
Иногда может оказаться полезной функция чтения или изменения атрибутов
файла.
Функции информационного обмена MS DOS в своем развитии имели
разные формы от специализированных программ обмена для каждого типа уст-
Язык Ассемблера в программировании информационных и управляющих систем 121
ройств до унификации обмена благодаря мощной файловой системе.
Первичные функции обмена использовали так называемый блок управления файлом
FCB, начальное состояние которого должен определять программист. Более
современным является метод дескриптора файла, по которому при выполнении
начальных функций open и create определяется путь доступа к файлу, после
чего устанавливается указатель файла и появляется возможность выполнения
функции записи/чтения, за каждой из которых зарезервирован отдельный
номер. Это упрощает программирование операций с файлами и позволяет
осуществлять обмен информацией между прикладной программой и файлами с
помощью одних и тех же функций, независимо от природы файлов. В частности
через файл может выполняться обмен со стандартными устройствами ввода-
вывода, такими как консоль связи с оператором CON или устройство печати
PRN. Удобство заключается и в том, что прикладная программа при создании
(или открытии существующего) файла передает DOS полное имя файла в виде
символьной строки, получает взамен от DOS описатель файла в виде
16-разрядного числа и затем все последующие операции с файлом выполняет через этот
описатель (handle). При загрузке MS DOS открывает пять стандартных устройств:
0 - устройство ввода (обычно клавиатура);
1 - устройство вывода (обычно экран дисплея);
2 - устройство вывода ошибок (всегда CON: - экран дисплея);
3 - внешнее устройство обмена AUX (асинхронный адаптер С0М1);
4 - принтер (первый параллельный порт принтера LPT1).
Ниже приводится краткое описание основных файловых функций. При
вызове любой из них код функции должен быть помещен в регистр АН,
дополнительные параметры обычно располагаются в регистрах AL, BX, CX, DX.
Возвращаемые значения в виде информации о файле или ошибках обычно
помещаются в регистры АХ и DX. Признаком успешного окончания любой из
операции служит флаг CF, который устанавливается в 0 при нормальном
завершении операции и в 1 при возникновении какой-либо ошибки. При этом в регистре АХ
возвращается код ошибки.
АН = ЗсН Создание файла
АН = 3dH Открытие файла
AL - режим открытия файла
DS:DX - адрес символьной строки спецификации файла
СХ - атрибут файла
Возврат: АХ - описатель файла (CF = 0); код ошибки (CF = 1).
АН = ЗеН Закрытие файла
ВХ - описатель файла
Возврат: АХ - код ошибки (CF = 1).
АН =41Н Удаление файла
DS:DX - адрес символьной строки спецификации файла
Возврат: АХ - код ошибки (CF = 1).
АН = 42Н Установить указатель в файле
ВХ - описатель файла
CX:DX - величина смещения указателя (СХ * 65536) + DX
Глава 6 Использование системы прерываний и программирование ввода-вывода
AL - код смещения указателя: 0 - от начала файла;
1 - от текущей позиции; 2 - от конца файла.
Возврат: DX:AX - новая позиция указателя файла (CF = 0).
АХ - код ошибки (CF = 1).
АН = 3fH Чтение из файла
АН = 40Н Запись в файл
ВХ - описатель файла
DS:DX - адрес буфера для приема или записи данных
СХ - количество читаемых байтов
Возврат: АХ - количество байтов, участвовавших в обмене
(CF=0); код ошибки (CF = 1).
Пример использования файловых функций в виде фрагмента
программы, которая выполняет перекодировку заданного текстового файла с именем,
заданным пользователем, из строчных в прописные буквы.
LEA DX,Prompt ; Загрузка адреса приемника сообщения
MOV АН, 9
INT 21п ; Запрос ввода имени файла
LEA DX,File_Name ; Загрузка адреса приемника
MOV АН,0Ah
INT 21h ; Ввод имени файла в форме: имя.расширение
; Подготовить ASCIIZ-строку с именем файла
ADD DX,2 ; DX - адрес начала текста имени файла
MOV BL, Filename+1 ; BL - длина введенного имени
SUB ВН,ВН -
MOV BYTE PTR [ВХ-1],0 ; Записать 0 в конец строки
MOV AX,3D02h ; Доступ для чтения и записи
INT 2 In ; Открыть файл
JC Error
MOV BX,AX ; Сохранение описателя файла
; Получить текущее значение указателя чтения/записи перед
чтением
RjSeek:SUB CX,CX
MOV DX,CX ; CX:DX = 0
MOV AX,4201h ; Функция определения позиции указателя
INT 21h
JC Error
PUSH DX
PUSH AX ; Запись текущей позиции указателя в стек
; Чтение очередных 128 байт из файла
MOV СХ,128 ; Длина буфера
LEA DX,Buffer ; Адрес буфера
MOV АН, 3Fh
INT 2 In
JC Error
OR AH,AL
JZ End File ; конец файла
Язык Ассемблера в программировании информационных и управляющих систем I 23
СМР АХ,СХ
JE Okey
SUB АН,АН ; признак конца файла
Okey: ; Вызов функции перекодировки блока
POP DX
POP CX ; CX:DX - позиция перезаписи
MOV AX74201h ; Функция определения позиции указателя
INT 21h
JC Error
MOV СХ,128 ; Длина буфера
LEA DX,Buffer ; Адрес буфера
MOV АН,4Oh
INT 21h
JNC RjSeek
JC Error
Для однопрограммных систем типа MS DOS драйверы [20] могут
состоять из заголовка, в котором в соответствии со стандартом устанавливаются
связи для просмотра драйверов всех устройств, процедуры стратегии,
запоминающей указатель на данные драйвера, и процедуры программного
прерывания, выполняет по выбору нужную функцию из комплекса. В комплекс
функций драйвера устройства по стандарту MS DOS входят следующие функции.
1. Инициализация (INITIALIZE), выполняющая настройку или
программирование или настройку состояния внешнего устройства и его
интерфейсных схем.
2. Проверка носителя (CHECK_MEDIA).
3. Создание блока параметров диска (МАКЕ_ВРВ).
4. Контроль ввода данных (IOCTL_IN).
5. Ввод данных (INPUTJDATA).
6. Проверка целостности входных данных (NONDISTRUCT_IN).
7. Чтение статуса ввода (INPUT_STATUS).
8. Очистка ввода (CLEARJNPUT).
9. Вывод данных (OUTPUT_DATA).
10. Проверка вывода (OUTPUT_VERIFY).
11. Чтения статуса вывода (OUTPUT_DATA).
12. Очистка вывода (CLEAR_OUTPUT).
13. Контроль вывода данных (IOCTL_OUT).
Обращение к этим функциям выполняются через стандартные функции
обмена программного прерывания 21 h и для пользователя они остаются
незаметными. Информацию об их наличии и контроле файлов и устройств можно
получить от функции контроля ввода-вывода 44h прерывания 21h.
6.3. Программирование ввода-вывода на физическом уровне
Общая схема процедуры обмена (ввода или вывода) одним физическим
элементом драйвера ввода-вывода включает такую последовательность действий
Глава 6 Использование системы прерываний и программирование ввода-вывода
1. Выдача подготовительной команды, включающей исполнительные
механизмы или электронные устройства.
2. Проверка готовности устройства к обмену.
3. Собственно обмен: ввод или вывод данных в зависимости от типа
устройства и нужной функции.
4. Сохранение введенных данных и подготовка информации о
завершении ввода-вывода.
5. Выдача заключительной команды, освобождающей устройство для
возможного использования в других задачах.
6. Выход из драйвера.
Эту последовательность действий для драйвера ввода устройства,
содержащего командный порт, управляющий действиями устройства, порт
состояния, контролирующей работоспособности и получение данных с устройства, и порт
данных для обмена данными, можно записать для однобайтного канала обмена
таким образом:
Загрузка управляющего кода включения
устройства
Пересылка кода включения в порт управления
Ввод содержимого порта состояний
Контроль по маске битов аварийного
состояния
обработку аварийного состояния устройства
Контроль готовности данных для ввода
На начало цикла ожидания готовности
Ввод данных
Загрузка управляющего кода выключения
устройства
Пересылка кода выключения в порт
drln PROC
MOV AL,cmOn
OUT cmPrt,AL
1:IN AL,stPrt
TEST AL,avMask
JNZ lErr ; Ha
TEST AL,rdyIn
JZ 1
IN AL,dtPrt
PUSH AX
MOV AL,cmOff
OUT cmPrt,AL
управления
POP AX
RET
drln ENDP
Номера управляющих портов (cmPrt и stPrt) и порта данных (dtPrt), a
также коды команд внешнего устройства (cmOn и cmOff) и маски контроля
разрядов порта состояния (avMask и rdyln) должны быть определены в начале
программы. Если номера портов имеют значения больше Offh, то команды IN и
OUT следует заменить парами операций:
MOV DX,cmPrt ; Подготовка адреса порта
OUT DX,AX ; Выдача команды на порт управления
Самым неэффективным местом драйвера с приведенной
последовательностью действий есть ожидание готовности внешнего устройства, которое логиче-
Язык Ассемблера в программировании информационных и управляющих систем 1 25
ски проще всего выполняется в форме циклического чтения порта или байта
состояния внешнего устройства с последующей проверкой бита готовности. Этот
цикл загружает процессор псевдовычислениями, которые вообще не приносят
никакой пользы. Для повышения эффективности обмена уже в первых
вычислительных системах стали использовать аппаратные прерывания, которые по сигналам
готовности от внешних устройств приостанавливали процесс обработки
информации по основной задаче и передавали управление специальным программам
обработчиков прерываний для немедленного обмена информацией.
6.4. Аппаратные прерывания и их применение для
организации информационного обмена
Типовое аппаратное обеспечение системы прерываний включает:
• специальные команды обращения к процедурам процессора,
программируемым на языках высокого уровня с определением атрибута
процедуры interrupt (для процессоров ix86 команда INT);
• специальные средства защиты процессора от ошибок и аварийных
ситуаций, вызывающих автоматическое обращение к процедурам
обработки прерываний при возникновении особых ситуаций;
• специальные средства отладки программ, которые позволяют
определять на машинном уровне контрольные точки и режимы отладки
программ;
• блок приоритетного прерывания (БПП), формирующий по сигналам от
внешних устройств управляющие сигналы на центральный процессор и
номера векторов прерывания.
Аппаратные прерывания, используемые для обработки вектора,
определяются конструкцией компьютера и, прежде всего, способом подключения
сигналов прерывания к БПП и способом его программирования в BIOS. Для
машин типа IBM/AT и последующих BIOS использует такие векторы прерываний
внешних устройств.
8 - прерывание системного таймера IRQ0, стандартно запускаемое 18,2
раза в секунду по каналу 0 системного таймера 8254 и используемое
для обновления текущего системного времени и даты.
9 - прерывание IRQ1, свидетельствующее о готовности данных на
клавиатуре.
Oah - прерывание IRQ2, свидетельствующее о приходе сигналов с
устройств через подчиненный БПП.
Obh - прерывание IRQ3, возникающее при необходимости обслуживания
последовательного порта COM2.
Och - прерывание IRQ4, возникающее при необходимости обслуживания
последовательного порта СОМ1.
Odh - прерывание IRQ5, сильно изменяется от системы к системе.
Oh - прерывание IRQ6, фирмируемое контроллером гибких дисков по
завершении операции.
1 26 Глава 6 Использование системы прерываний и программирование ввода-вывода
Ofh - прерывание IRQ7, формируемое параллельным портом LPT1.
70h - прерывание часов реального времени IRQ8, обеспечивающее
возможность подачи сигнала будильника или периодического
инициирующего прерывания.
71h - прерывание IRQ9 перенаправляется BIOS на INT Oah.
72h - прерывание IRQ 10 зарезервировано IBM, но может использоваться
для подключения нестандартных устройств.
73h - прерывание IRQ11 зарезервировано IBM, но может использоваться
для подключения нестандартных устройств.
74h - прерывание IRQ 12 обрабатывает сигнал от мыши или другого
координатного устройства.
75h - прерывание IRQ 13 обрабатывает исключительные ситуации
математического сопроцессора.
76h - прерывание IRQ 14 возникает при завершении работы жесткого диска.
77h - прерывание IRQ15 зарезервировано IBM, но может использоваться
для подключения нестандартных устройств.
Организация обработки аппаратных прерываний обеспечивается
процедурами, получившими название обработчиков прерываний (interruption
handler) и выполняющими самостоятельные вычислительные процессы,
инициированные сигналами с внешних устройств. Вообще последовательность
действий обработчика близка к последовательности действий драйвера, но
имеет несколько существенных особенностей:
• при входе в обработчик прерываний обработка новых прерываний
обычно запрещается;
• необходимо сохранить содержимое регистров, изменяемых в обработчике;
• поскольку прерывание может приостановить любую задачу, то нужно
позаботиться о корректном состоянии сегментных регистров в начале
обработчика или в процессе переключения задач;
• выполнить базовые действия драйвера;
• для того, чтобы разрешить обработку новых прерываний через этот
блок необходимо выдать на БПП последовательность кодов окончания
обработки прерывания (end of interruption) EOI;
• выдать синхронизирующую информацию готовности буферов для
последующих обменов со смежными процессами;
• если есть необходимость обратиться к действиям, которые связанны с
другими аппаратными прерываниями, то это целесообразно сделать
после формирования EOI, разрешив после этого обработку аппаратных
прерываний командой STI;
• перед возвратом к прерванной программе нужно восстановить
регистры, испорченные при обработке прерывания.
В конце кода каждого из обработчиков аппаратных прерываний
необходимо включать следующие 2 строчки кода для главного БПП, если
обслуживаемое прерывание обрабатывается главным БПП:
Язык Ассемблера в программировании информационных и управляющих систем I 27
MOV AL,20H
OUT 20H,AL ; Выдача EOI на главный БПП
и еще 2 дополнительные строчки, если обслуживаемое прерывание
обрабатывается вспомогательным БПП компьютеров типа AT и более поздних:
MOV AL,20H
OUT 0A0H,AL ; Выдача EOI на БПП-2
Два одинаковых числа (20Н) в обеих строках - это просто совпадение.
Если аппаратное прерывание не заканчивается этими строками, то микросхема
8259 в режиме применяемом в MS DOS не очистит информацию регистра
обслуживания, с тем чтобы была разрешена обработка прерываний с более
низкими уровнями, чем только что обработанное. Отсутствие этих строк легко
может привести к краху программы, так как прерывания от клавиатуры, скорее
всего, окажутся замороженными и даже Ctrl-Alt-Del окажется бесполезным.
Более того, эти строки должны быть выданы вместе с командой STI,
разрешающей прерывания перед любым обращением к программам DOS, связанным с
этими БПП, чтобы обеспечить возможность выполнения ввода-вывода по
прерыванию.
Одной из причин написания обработчика прерывания может быть
использование какого-либо отдельного аппаратного прерывания. Это
прерывание автоматически вызывается при возникновении определенных условий в
вычислительной системе. В некоторых случаях BIOS инициализирует вектор
этого прерывания так, что он указывает на процедуру, которая вообще ничего
не делает, так как содержит один оператор IRET. Вы можете написать свою
процедуру и изменить вектор прерываний, чтобы он обеспечивал ее
выполнение при возникновении аппаратного прерывания. Наконец, вы можете
написать прерывание, которое полностью заменит одну из процедур операционной
системы, приспособленное к вашим программным нуждам.
В ОС для работы с дисковыми файлами часто включают специальные
драйверы, выполняющиеся на уровне отдельных буферных задач ввода-вывода
по информационному обмену между устройствами и буферами. Построение
драйверов с использованием системы прерываний требует дополнительных
действий по синхронизации вычислительных процессов. При этом наиболее
распространена ситуация, когда один из процессов является источником
данных, а другой приемником или потребителем.
6*5. Синхронизирующие примитивы, их
реализация и использование
Использование системы аппаратных прерываний создает условия для
возникновения и развития в ВС многочисленных вычислительных процессов.
При выполнении информационного обмена в таких условиях возникает
проблема синхронизации процессов обработки аппаратных прерываний с
процессами производителями и потребителями данных. Термин ''процесс" использу-
I 28 Глава 6 Использование системы прерываний и программирование ввода-вывода
ется достаточно произвольно, в расчете на интуитивное понимание его
значения. Общепринятое, но неформальное определение процесса таково:
последовательный процесс, который иногда называют "задачей", это работа,
выполняемая последовательным процессором по программе над ее данными.
С логической точки зрения каждый процесс [49] должен иметь свой
собственный процессор или оборудование для его развития и управляющую
программу. Более того, для целесообразности создания процесса необходимо
некоторое оборудование, внешнее относительно главного процессора, на котором
выполняются действия, параллельные действиям процессора. На практике два
разных процесса могут разделять одну и ту же программу или один и тот же
процессор.
Развитие процесса может быть описано как последовательность
векторов состояния Si, S2,...Sn, где каждый вектор состояния Si содержит указатель на
следующую команду программы, которую нужно выполнить, а также значения
всех промежуточных переменных, определяемых в программе. С другой точки
зрения вектор состояния процесса р составляет та информация, которая
необходима процессору, чтобы направить продолжение развития процесса р.
Вектор состояния процессар может быть заменен или при развитии/?, или при
развитии других процессов, разделяющих некоторые компоненты вектора
состояния из р.
Взаимосвязи между процессами и управление их работой могут
устанавливаться через общие разделяемые переменные и специальные базовые
операции синхронизации процессов (примитивы), определенные ниже. Если мы
рассмотрим процесс в любой момент времени, то он может быть или активным
или блокированным. Процесс/? будет логически выполняемым, если он или
выполняется процессором, или мог бы выполняться процессором, если бы
процессор был доступным. В последнем случае мы часто будем говорить, что процесс
находится в состоянии готовности. Процесс р блокирован, если он не может
протекать пока не получит сигнал, сообщение или ресурс от некоторого
другого процесса.
За счет анализа исключительно логики процессов и игнорирования
числа доступных физических процессоров мы можем обеспечить
процессорно-независимые решения для ряда системных проблем. Эти решения будут
гарантировать нам, что система процессов составлена правильно независимо от того,
разделяют ли процессы физические процессоры или нет. Концепция процесса
имеет несколько других важных применений в ОС. Она обеспечила выделение
и определение многих базовых задач ОС, упростила изучение их организации и
динамики и привела к развитию полезных методологий проектирования, одной
из главных тем которых является взаимодействие процессов. Процессы будут
определяться их программами, а нотация par будет использована для
условного представления параллелизма.
Если несколько процессов могут асинхронно изменять содержание
общей области данных, необходимо защитить данные от одновременного доступа
и их изменения двумя или большим количеством процессов. Разделяемая
область в общем случае может и не сохранить изменений, если защита не
обеспечена. Эту ситуацию можно проиллюстрировать на некоторых примерах буфе-
Язык Ассемблера в программировании информационных и управляющих систем 1 2Q
ризации ввода-вывода. Общие данные, разделяемые несколькими процессами,
чаще всего описывают как ресурс; обновление данных соответствует
распределению или освобождению элементов ресурса. Рассмотрим два процесса pi и р2,
подсчета единиц, асинхронно увеличивающих значения общей переменной х,
представляющей количество свободных мест в системе продажи билетов на
транспортные средства.
Если один из процессов получит значение х после того, как другой
процесс уже воспользовался предыдущим значением х, но еще не сформировал
свой результат, то результат процесса, закончившегося последним, фактически
исключит учет результата процесса, закончившегося раньше. Это приведет к
существенным ошибкам в функционировании информационной системы. Ясно,
что должно учитываться каждое приращение х. Простейшее решение задачи на
языках высокого уровня состоит в разрешении входить в любой момент
времени в критическую секцию (КС), выполняющую наращивание только для одного
процесса [49]. В общем случае критическая секция может состоять из любого
числа предложений, например, манипуляций с буфером.
Теперь можно более точно определить проблему и вопросы, имеющие к
ней отношение. Нам даны несколько последовательных процессоров, которые
могут связываться друг с другом через общую память, сохраняющую данные.
Каждая из программ, циклически повторяемых процессорами, содержит КС, в
которой организован доступ к общему данному. Проблема состоит в том,
чтобы запрограммировать процессоры так, чтобы в любой момент только один из
процессоров находился в своей КС. Если процессор С входит в свою КС, то
никакой другой процессор не может сделать то же самое до тех пор, пока С не
оставит свою КС.
Относительно системы команд сделаны следующие допущения.
1. Считывание с общей памяти и запись в нее есть неделимые операции;
одновременные обращения (на запись или на считывание) к одной и той же
ячейке памяти более одного процессора приведут к последовательным
обращениям в неизвестном порядке.
2. КС не имеют связанных с ними приоритетов.
3. Относительные скорости процессоров неизвестны.
4. Программа может приостанавливаться только за границами ее КС.
Для реализации первого допущения в ассемблере ix86 используется
префикс LOCK, который предотвращает любое обращение к памяти на период
выполнения команды, включая чтение данных и запись результата. Так команда
LOCK ADD PlcCntr,AX
пресечет любые попытки обращения к памяти в процессе выполнения команды.
Но она не решает задачу управления КС, так как, не учитывает возможного
вхождения в КС другого процесса.
Проблема может также быть сформулирована в терминах нескольких
процессов, асинхронно разделяющих во времени единственный процессор.
Удобно забыть о количестве физических процессоров и думать о множестве
5 В.И. Пустоваров
I 30 Глава 6 Использование системы прерываний и программирование ввода-вывода
почти независимых процессов. Программное решение проблемы КС имеет два
недостатка.
1. Сложность и неясность решения: простое концептуальное требование
на выполнение взаимного исключения в КС приводит к сложным и неудобным
дополнениям программ.
2. На протяжении времени, когда один процесс находится в своей КС,
другой процесс может бесконечно циклиться, как в случае ожидания
готовности, и при этом проверять общие переменные. Чтобы сделать это, ожидающий
процессор должен отбирать циклы памяти у активного процессора, в
действительности ничего полезного не выполняя. Результатом будет общее замедление
системы действиями, которые не выполняют никакой полезной работы.
В 1965 году Дейкстра предложил два примитива [49], значительно
упростивших взаимосвязь и синхронизацию процессов. В абстрактной форме эти
примитивы, обозначаемые Р и V, оперируют с неотрицательными целыми
переменными, называемыми семафорами. Если S - такой семафор, операции
выполняются следующим образом:
1. Операция V(S): переменная S увеличивается на 1 (инкрементируется)
одним неделимым действием; выборка, инкремент и запоминание не могут
быть разорваны, и к S нет доступа от других процессов во время операции. Эта
операция может быть выполнена одной машинной командой с префиксом
LOCK:
LOCK INC PlcCntr;
но при этом нужно учитывать отсутствие контроля переполнения счетчика.
2. Операция P(S): уменьшает S на 1, если это возможно. Если S = 0, то
оно не может уменьшиться и остаться в классе целых неотрицательных
значений; тогда процесс, вызвавший Р-операцию, ожидает, пока это уменьшение не
сшнет возможным. Успешная проверка и уменьшение S - также неделимая
операция. Для ее реализации необходимо организовать блокировку других
процессов на несколько команд, например с помощью переменной флага взаимного
исключения, обозначенной далее MUTEX.
Если несколько процессов одновременно запрашивают Р- или К-опера-
ции над одним и тем же семафором, то эти операции будут выполняться
последовательно в произвольном порядке; аналогично, если больше одного процесса
ожидает при выполнении Р-операции и изменяемый семафор становится
положительным, то конкретный ожидающий процесс, выбранный для завершения
операции будет произвольным. Именно семафорные переменные чаще всего
используются для синхронизации процессов. Р-примитив включает в себя
потенциальное ожидание вызывающих процессов, в то время как К-примитив,
вызванный из процесса освобождающего ресурс, может активизировать
некоторый ожидающий процесс. Неделимость Р и F-операции гарантирует
целостность значений семафоров.
На уровне машинных операций в многопроцессорном вычислительном
комплексе такую неделимость Р-операции можно обеспечить только взаимным
Язык Ассемблера в программировании информационных и управляющих систем 131
исключением одновременного входа в Р-и F-операции в прологах подпрограм-
м этих операций
LCS: MOV AL,1
LOCK XCHG AL,MUTEX ; Попытка занять MUTEX
JNZ LCS
Цикл с меткой LCS полностью соответствует ожиданию готовности в
КС и, чтобы избежать потерь времени при занятости MUTEX - лучше
обратиться к ОС. В эпилоге процедуры нужно освободить MUTEX командами
XOR AL,AL
LOCK XCHG AL,MUTEX ; Освобождение взаимного
; исключения возможного перехода
Семафорные операции дают простое и непосредственное решение
проблемы КС. Пусть S - семафор, используемый ниже для защиты КС.
Программным решением проблемы для п процессов, работающих параллельно, с
использованием расширения С, будет наделимость Р-операции.
{semaphore S; S = 1; ...
{/* процесс Pi:*/ while (i){ P(S); CS[i]; V(S); prg() ;}
par ...
Значение 5 равно О, когда некоторый процесс находится у своей КС;
иначе 5=1. Семафор выполняет функцию простого замка. Взаимное
исключение гарантировано, поскольку только один процесс может уменьшить 5 до
нуля с помощью операции; все остальные процессы, пытающиеся войти в свои
КС, когда 5 = 0, будут вынуждены ожидать по P(S). Взаимное блокирование
невозможно, потому что одновременные попытки войти в КС, когда 5=1,
должны по определению превратиться в последовательность Р-операций.
Когда семафор может иметь только значения 0 или 1, его называют двоичным
семафором.
Каждый процесс в ВК может характеризоваться количеством и типом
потребляемых (используемых) и производимых (освобождаемых) ресурсов. Это
могут быть физические ресурсы, такие, как главная память, накапливающие
устройства, механизмы или процессоры, или интегрированные и виртуальные
ресурсы, такие, как заполненные буферы, КС, реентрантные программы,
допускающие повторный вход для выполнения задания, и частичные виртуальные
ресурсы, например, файлы, выделенные в дисковом накопителе и др. Семафоры
могут быть использованы для учета ресурсов и синхронизации процессов, а
также для запирания КС. Например, процесс может блокироваться Р-операци-
ей на семафоре 5 и может быть разблокирован другим процессом,
выполняющим V(S). Один из процессов Pi можно рассматривать как потребляющий
ресурс, использующий команду P(S), в то время как Р2 производит единицы
ресурса 5 командами V(S).
Глава 6 Использование системы прерываний и программирование ввода-вывода
Рассмотрим два процесса, связанные через буферную память. Процесс-
производитель формирует информацию и потом добавляет ее в буферную
память; одновременно с этим процесс-потребитель извлекает информацию из
буферной памяти и потом обрабатывает ее. Это обобщает ситуацию, при которой
основной процесс будет формировать выходную запись и потом передавать ее
к следующему доступному буферу. В тоже время процесс вывода извлекает
запись асинхронно из буферной памяти и потом передает ее на устройство
вывода. Пусть буферная память состоит из N буферов одинакового размера, причем
каждый буфер может сохранять только одну запись.
Будем использовать два семафора как счетчики ресурсов: е - количество
пустых буферов и/- количество заполненных буферов. Добавление данных к
буферу и извлечение данных из него создают КС; добавим Ъ - двоичный
семафор, используемый для взаимного исключения. Тогда процессы могут быть
описаны таким образом:
{semaphore e, f, b; e=N; f = 0; b = 1;
{/* производитель */ while(1)
{// формирование следующей записи
Р(е); Р(Ь); // добавление к буферу
V(b); V(f);} par
{/* потребитель */ while(1){Р(f); P(b); // взятие из
буфера
V(b); V(e); /* выполнение записи */}
Операции увеличения и уменьшения ей f должны быть неделимыми,
иначе значения этих переменных могут стать некорректными. Использование
КС требует включения дополнительной логики для организации ожидания при
Р-операциях. Описанное взаимное исключение при манипуляциях с буфером,
возможно, и не было необходимым; но, если использованные связанные списки
буферов или программа обобщается на т производителей и п потребителей (т,
п > 0), взаимное исключение необходимо. К программам такого типа относят
так называемые спулеры, обеспечивающие ввод-вывод параллельно с
обработкой других задач (spool означает сокращенно Simultaneous Peripheral
Opepations On Line, т.е. непосредственная периферийная обработка в
оперативном режиме). Такие программы могут использоваться как в однопрограммных
ОС (например, MS DOS) для фоновой распечатки и параллельного ввода, так и
в сетевых системах для организации сетевых принтеров, которые для
эффективной работы требуют либо автоматической подачи бумаги, либо четких
действий квалифицированного человека-оператора.
Другие типовые примитивы составляют события или сигналы,
сообщения и очереди. Примитивы типа сигналов обычно связывают с блокированием
и разблокированием процессов. Наиболее общая реализация может иметь
несколько каналов блокирования-разблокирования по количеству разрядов в
области, сохраняющей состояние выполняемого процесса или задачи. События
обычно имеют более общий характер и не связываются непосредственно с
задачей. Чаще всего события связывают с одним процессом потребителем, но у них
Язык Ассемблера в программировании информационных и управляющих систем I 33
может быть несколько источников и они могут группироваться для проверки
нескольких конъюнктивных условий.
Взаимосвязь процессов с позиций объектного подхода в основном
включает передачу сообщений между процессами. В этом контексте мы можем
рассматривать семафор как определение очереди из нулевых сообщений, а
операции Р и V как низкоуровневые операции передачи сообщений. F-операция
добавляет сообщение к очереди, в то время как Р-операция извлекает его из
очереди, если это возможно. Значением семафора будет количество таких
сообщений в очереди, если мы сохраним наше начальное ограничение значений
неотрицательными целыми. Интересное и элегантное множество примитивов для
передачи сообщений используется в ОС семейства irmx фирмы Intel,
ориентированных на работу в реальном времени.
1. SendMessage(C, В) - послать сообщение в буферный пул.
2. В = RecieveMessage(C) - получить сообщение из буферного пула.
Для сообщений следует различать два типа обмена. При первом из них
используется так называемый почтовый ящик (mailBox), в который
вкладывается сообщение и посылается определенным именованным каналом передачи
сообщений. Тут важно отметить, что допустимо любое количество процессов-
поставщиков и процессов-пользователей. Причем сообщения создают очередь
FIFO, распределенную почти в произвольной последовательности в системной
памяти ОС. Количество сообщений ограничено только наличием свободной
системной памяти, а очередь организуется в форме одно- или двухсвязных
списков.
Более ранняя трактовка нормального протокола сообщений требовала
использования четырех примитивов: поставщик выдает SendMesscge, после
которого необходимо получение подтверждающего сообщения о получении
переданного сообщения процессом-приемником функцией WaitAnswer. В то время
как получатель выдает WaitMessage и последовательно за ним SendAnswer.
Такая система генерирует фиктивный ответ, когда процесс-потребитель,
которому адресуется сообщение, не существует. Идентификаторы поставщика и
потребителя сообщения запоминаются в буфере, чтобы позволить системе
проверять тождественность процессов при подтверждении. В этом случае
определяются некооперированные процессы, которые умышленно или ошибочно
пытаются взаимодействовать с помощью сообщений.
В ОС Windows, Windows NT и Windows 95 в качестве базовых
примитивов используются:
• взаимные исключения MUTEX и критические секции (Critical Section)
для блокирования доступа более, чем одному процессу или подзадаче;
• семафоры (Semaphore) - для ограничения числа подзадач имеющих
одновременный доступ к ресурсам;
• события - для одновременной сигнализации нескольким процессам и
подзадачам.
1 34 Глава 6 Использование системы прерываний и программирование ввода-вывода
Учитывая распространенность очередей при решении задач синхронизации
процессов и почти фундаментальную их роль в обмене информацией остановимся
на возможных вариантах реализации очередей по следующим требованиям:
• тип элемента очереди должен быть постоянным, но при ее создании
может быть выбран произвольно;
• связь структурного элемента очереди с его содержательной частью
осуществляется указателем произвольного типа.
• На этой основе может быть построено два типа очередей в форме:
• массива фиксированной длины;
• и структуры указателей с цепочками последовательно связанных элементов.
Первый тип опирается на решение задачи динамического распределения
памяти, выполняемого иногда специальными функциями ОС или языка
программирования и использует сформированные указатели, а второй - для
уменьшения пересылок строится в форме процедур кольцевого обслуживания
элементов очереди, составляющих массив. Для реализации второго подхода
используются указатели-ограничители и текущие указатели элементов. По
организации очереди разделяют на наиболее распространенную дисциплину FIFO
(First Input - First Output или первым введенный - первым выведенный) и более
характерную для стека - LIFO. Важное место занимает порядок обработки
переполнения очереди, где выбирается один из трех вариантов: удаление самого
старого запроса, устранение последнего запроса или переход к состоянию
ожидания свободного места в очереди.
Ниже приведен пример организации очереди для организации FIFO со
статическим распределением памяти, циклической организацией буфера и
игнорированием запросов по заполнении очереди:
queue macro w, ran,Ing
nm hd_queue <lst&nm,Ist&nm, fin&nm>
lst&nm d&w Ing dup(?)
fm&nra equ $
endm
hd_queue struc
freeptr dw ? ; Указатель свободной части очереди
blckptr dw ? ; Указатель занятой части очереди
limtptr dw ?
hd_queue ends
sd segment ; *** Начало сегмента данных ***
queue w, ex^_queue, 10 ; Определение очередей
queue d,ed_queue,16
sd ends ; *** Конец сегмента данных ***
.CODE
writeq proc ; Аргумент - дальний указатель на
; заголовок
push bp
mov bp,sp
les bx,[bp+6] ; Загрузка указателя заголовка
Язык Ассемблера в программировании информационных и управляющих систем
135
mov
cmp
jnae
mov
lnw: cmp
jz
mov
stosw
mov
Iwt:pop
ret
writeq
readq
push
mov
Ids
cli
mov
cmp
jnae
mov
lnr: cmp
jz
lodsw
mov
lrt:pop
ret
readq
di,es:freeptr[bxj
di,es:limtptr[bx]
lnw
di,6
di,es:blckptr[bx}
Iwt
ax,[bp+4]
• Загрузка указателя свободной
• части
• Контроль предела массива
; Циклический возврат к началу
; Контроль наличия свободного
; места
} Переход при отсутствии
; Сохранение семантической части запроса
es:freeptr[bx],di
bp ; zf - 0 -
; Сохранение указателя свободной
; части
признак отказа в постановке
; в очередь
endp
proc
bp
bp,sp
bx,[bp+6]
si,blckptr[bx}
; части
si,limtptr[bx]
lnr
si, 6
di,freeptr[bx]
lrt
ds:freeptr,si
bp
endp
Загрузка указателя заголовка
Загрузка указателя заполненной
Контроль предела массива
Циклический возврат к началу
Контроль наличия элементов
Переход при отсутствии
zf = 0 - признак пустой очереди
Все примитивы, рассмотренные в этой главе, содержат в себе
возможность логического блокирования и активизации процессов. Но потенциально
блокирование и активизация процессов происходит и тогда, когда занимаются
или освобождаются ресурсы любого типа, например, вследствие запроса или
освобождения блоков основной памяти. Мы подробнее рассмотрим этот более
общий случай после изучения дополнительных аспектов систем
мультипрограммирования.
Программные прерывания в ОС обеспечивают обращения к системным
программам информационного обмена и супервизорного обслуживания. Они
позволяют использовать фиксированный для заданной группы функций вектор
прерывания с передачей в форме параметров номеров функций, подфункций и
др. Использование номеров векторов для вызовов прерываний, выполняющих
системные функции и процедуры, снимает ряд осложнений с переместимостью
программ при их загрузке в память и динамических изменениях при их
выполнении.
■ об Глава 6 Использование системы прерываний и программирование ввода-вывода
6.6. Проектирование программных прерываний
и резидентных программ для MS DOS
Разместить пользовательские или системные программы для
длительного хранения в памяти, или подготовить их для функционирования в так
называемом резидентном варианте (TSR - Task Style Resident) можно
ознакомившись с основными приемами работы с системой прерываний MS DOS и
овладев методикой отладки резидентных программ. Для этого нужны знания о
способах запуска и выполнения программ в MS DOS, понятие о TSR-программе и
механизме ее инициализации [20].
Выполняемые программы хранятся на дисковых накопителях в одном из
двух форматов: .ЕХЕ или .СОМ. Программы первого типа могут быть больше
64К, но они требуют некоторой обработки перед тем, как DOS загрузит их в
память, в то время, как СОМ-программы существуют прямо в том формате,
который нужен для загрузки в память. СОМ-программы особенно полезны для
коротких утилит. В обоих случаях код программы предваряется в памяти
префиксом сегмента программы (PSP). Это область размером 100Н' байтов,
которая содержит информацию, необходимую DOS для выполнения программы;
PSP также обеспечивает место для файловых операций ввода-вывода. При
загрузке ЕХЕ-файла и DS и ES указывают на PSP. Для СОМ-файлов CS также
сначала указывает на PSP. Отметим, что MS DOS 3.0 имеет функцию, которая
возвращает номер сегмента PSP. Это функция 62Н прерывания 21Н, на вход
которой подается только номер, а в ВХ возвращается номер параграфа.
Одна из причин, по которой интересно положение PSP, состоит в том,
что его первое слово содержит номер прерывания DOS, которое будет
приводить к завершению программы. Когда выполняется последняя команда
программы - RET, то значения на вершине стека указывают счетчику команд
(регистр IP) на начало PSP, таким образом, код завершения выполняется как
следующая инструкция программы. Для справки приведем описание полей PSP
(см. табл. 6.1).
Большинство программ загружаются в память, запускаются, а затем
удаляются ОС по их завершении. Некоторые программы действуют как
драйверы устройств или обработчики прерываний и они должны быть сохранены в
памяти в качестве резидентных после завершения их инициализации. При этом
позже они могут использоваться через механизм векторов прерываний и
команд INT. Когда MS DOS загружает программу, то она помещается в область
памяти с минимальными адресами, сразу же за COMMAND.COM и
установленными драйверами устройств или другими служебными программами,
которые хранятся постоянно (резидентно) в памяти весь период функционирования
системы. В этот момент времени вся свободная память MS DOS,
расположенная за выполняемой программой, отведена этой программе и перераспределить
ее можно только специальными функциями DOS. Если программе нужна
память для создания области данных, то она может приближенно вычислить, где
в памяти кончается ее код и затем поместить требуемую область данных в
любое место за концом кода.
Язык Ассемблера в программировании информационных и управляющих систем 1 37
Таблица 6,1
Смещение
ОН
2Н
4Н
6Н
АН
ЕН
12Н
16Н
2СН
2ЕН
5СН
6СН
80Н
Размер поля
DW
DW
DW
DD
DD
DD
DD
22 байта
DW
46 байтов
16 байтов
20 байтов
128 байтов
Значение
номер функции DOS завершения программы
размер памяти в параграфах
резерв
длинный вызов функции диспетчера DOS
адрес завершения (IP,CS)
адрес выхода по Ctrl-Break (IP,CS)
адрес выхода по критической ошибке
резерв
номер параграфа строки среды
резерв
область параметров 1 (формат FCB)
область параметров 2 (формат FCB)
область DTA по умолчанию получает
командную строку программы
В операционной системе MS DOS возможно построение особого класса
программ, которые будучи однократно загруженными, продолжают
функционировать в течение всего времени функционирования MS DOS, если они не
будут деинициализированы специальными средствами. Отметим, что большая
часть команд самой MS DOS загружается в резидентную память этим же
методом. Особенностью программ этого класса является то, что после их
инициализации управление возвращается программе командного процессора и
пользователь имеет возможность запускать другие программы. Тем самым
обеспечивается одновременная работа нескольких программ. Передача управления
программам, инициализированным как резидентные, происходит "по мере
необходимости" в соответствии с происходящими в системе событиями, как правило,
с помощью аппаратных прерываний. Использование механизма резидентных
программ позволяет обеспечить такие характеристики вычислительной
системы как асинхронность и реактивность. Как правило, в виде TSR-программ
оформляются программы драйверов внешних устройств, менеджеры сетевых
протоколов, программы ведения системных журналов,
информационно-справочного сервиса и т.п.
Программы, оставленные резидентными в главной памяти, могут
служить в качестве служебных для других программ. Обычно такие программы
вызываются через неиспользуемый в конкретной конфигурации ВС вектор
прерывания. MS-DOS рассматривает такие программы как часть операционной
системы, защищая их от наложения других программ, которые будут
загружены впоследствии. Резидентные программы обычно пишутся в форме СОМ, так
как программы, оформленные в виде ЕХЕ-файлов оставить резидентными в
памяти несколько сложнее.
Завершение программы прерыванием 27Н оставляет ее резидентной в
памяти, если CS указывает на начало PSP, что автоматически обеспечивается в
СОМ-файлах, поэтому такую программу надо просто завершить прерыванием
138
Глава 6 Использование системы прерываний и программирование ввода-вывода
27Н. В ЕХЕ-программах, CS первоначально указывает на первый байт,
следующий за PSP (т.е. 100Н). При нормальном завершении ЕХЕ-программы
последняя инструкция RET выталкивает из стека первые значения, записанные
туда команды: PUSH DS / MOV АХ,0 / PUSH AX. Поскольку DS
первоначально указывает на начало PSP, то при получении этих значений из стека счетчик
команд указывает на смещение 0 в PSP, где при инициализации записывается
инструкция INT 20H. Поэтому при входе выполняется INT 20H, а это
стандартная функция для завершения программы и передачи управления в DOS.
Чтобы заставить прерывание 27Н работать в ЕХЕ-программе надо поместить
27Н во второй байт PSP (первый содержит машинный код инструкции INT), a
затем завершить программу обычным RET FAR. Для обоих типов файлов,
прежде чем выполнить прерывание 27Н, DX должен содержать смещение конца
программы, отсчитываемое от начала PSP.
Вектор прерывания устанавливается с помощью функции 25Н
прерывания 21Н. Позаботьтесь, чтобы процедура оканчивалась IRET. Кроме самой
процедуры, устанавливаемая программа не должна делать ничего, кроме
инициализации вектора прерывания, записи в DX значения длины в байтах
процедуры, оставляемой резидентно, и завершения. Для СОМ-файлов просто
поместите оператор INT 27H в конец программы. Для ЕХЕ-файлов поместите этот
оператор в первое слово PSP и завершите программу обычным оператором
RET. Для того чтобы выполнить процедуру, в последствии загруженная
программа должна вызвать INT 70H.
Приведены примеры для обоих типов выполняемых файлов (СОМ и
ЕХЕ). В обоих установлена метка intFIN для отметки конца процедуры
прерывания (напомним, что знак $ дает значение счетчика команд в этой точке). Для
СОМ-файлов intFIN дает смещение от начала PSP, как и требуется для
прерывания 27Н. Для ЕХЕ-файлов смещение отсчитывается от первого байта,
следующего за PSP, поэтому к нему необходимо прибавить 100Н, чтобы
пересчитать на начало PSP. Заметим, что поместив процедуру в начало программы, мы
можем исключить установочную часть кода из резидентной порции. Другой
возможный фокус состоит в использовании инструкции MOVSB для пересылки
кода процедуры вниз в неиспользуемую часть PSP, начиная со §мещения 60Н,
что освобождает 160 байтов памяти. Случай СОМ-файла:
BEGIN: JMP SHORT SBTJ3P ; Переход на установку
intROOT PROC FAR
PUSHA ; Сохранение регистров
PUSH DS
твло процедуры
РОРА ; Восстановление регистров
POP DS
IRET ; Выход из прерывания
intFIN EQU $ ; Адрес конца резидентной части
intROUT ЕШР
; Переключение вектора прерывания
Язык Ассемблера в программировании информационных и управляющих систем 1 3Q
SETJ3P: MOV DX,OFFSET intROUT ; Смещение входной точки
; в DX
MOV AL,7ОН ; Номер вектора прерывания
MOV АН,25Н ; Функция установки вектора
INT 21H ; Установка вектора
; Выход с сохранением резидентной части
LEA DX,intFIN ; Размер резидентной области
INT 27H ; Завершение
Случай файла ЕХЕ:
JMP SHORT SET__UP ; переход на установку
; Процедура обработчика прерывания
intROUT PROC FAR
PUSHA ; Сохранение регистров
PUSH DS
тело процедуры
РОРА ; Восстановление регистров
POP DS
IRET ; возврат из прерывания
intFIN EQU $ ; отметка конца процедуры
intROUT ENDP
; Переключение вектора прерывания
SET_tJP: MOV DX,OFFSET intROUT ; Смещение входной точки
; в DX
MOV AL,70H ; Номер вектора прерывания
MOV АН,25Н ; Функция установки вектора
INT 21H ; Установка вектора
; Выход с сохранением резидентной части
MOV DX,FINISH+100H ; Загрузка смещения конца
MOV BYTE PTR ES:[1],27H ; Коррекция PSP
RET ; Завершение установки
Функция 31Н прерывания 21Н работает аналогично, за исключением
того, что в DX должно содержаться число 16-байтных параграфов, требуемых
процедуре и вычисленных как размер области кодов процедуры от начала PSP.
Преимуществом этой функции является то, что она передает вызывающей
программе код выхода, дающий информацию о статусе процедуры. Вызывающая
программа получает этот код с помощью функции 4DH прерывания 21Н. В
настоящее время это считается более современным способом создания TSR-npo-
граммы.
Функция 25Н прерывания 21Н устанавливает вектор прерывания на
указанный адрес. Адреса имеют размер - два слова. Старшее слово содержит
значение сегмента (CS), младшее содержит смещение (IP). Чтобы установить
вектор, указывающим на одну из ваших процедур, нужно поместить сегмент
процедуры в DS, а смещение в DX, следуя порядку нижеприведенного примера.
Затем поместите номер прерывания в AL и вызовите функцию. Любая
процедура прерывания должна завершаться не обычной инструкцией RET, a IRET.
1 40 Глава 6 Использование системы прерываний и программирование ввода-вывода
IRET выталкивает из стека три слова, включая регистр флагов, в то время как
RET извлекает из стека только первые два. Отметим, что функция 25Н
автоматически запрещает аппаратные прерывания при изменении вектора, поэтому не
существует опасности, что при ее выполнении произойдет аппаратное
прерывание, использующее данный вектор.
; установка прерывания
PUSH DS ; сохраняем DS
MOV DX,OFFSET intROUT ; смещение для процедуры в DX
MOV AX,SEG intROUT ; сегмент процедуры
MOV DS,AX ;помещаем в DS
MOV АН,25Н /функция установки вектора
MOV AL,60H ;номер вектора
INT 21H ; меняем прерывание
POP DS ;восстанавливаем DS
; процедура прерывания
intROUT PROC FAR
PUSHA ; Сохраняем все изменяемые регистры
РОРА ; Восстанавливаем регистры
MOV AL,2QH ; Эти две строки EOI используем
OUT 20H,AL ; Только для аппаратных прерываний
IRET
intROUT ENDP
Отметим, что добавка EOI не нужна для тех векторов прерываний,
которые являются расширениями существующих прерываний, таким как
прерывание 1СН, которое добавляет код к прерыванию времени суток.
Когда программа завершается, должны быть восстановлены
оригинальные векторы прерываний. В противном случае последующая программа может
вызвать данное прерывание и передать управление на то место в памяти, в
котором процедура пользователя может быть уничтожена. Функция 35Н
прерывания 21Н возвращает текущее значение вектора прерывания, помещая
значение сегмента в ES, а смещение в ВХ. Перед установкой своего прерывания
желательно получить текущее значение вектора, используя эту функцию, для
сохранения этого значения, а при завершении работы восстановить их с
помощью функции 25Н перед завершением своей программы. Например:
; В сегменте данных:
KEEP^IP DW 0 ; Хранит смещение прерывания
KEEP__CS DW 0 ; Хранит сегмент заменяемого прерывания
; В начале программы
M0V АН,25Н ; функция получения вектора
MOV AL,1CH ; номер вектора
INT 21H ; теперь сегмент в ES, смещение в ВХ
M0V КЕЕР_1Р,ВХ ; Сохранение смещения
MOV KEEP_CS,ES ; Сохранение сегмента
CLI
PUSH
MOV
MOV
MOV
MOV
MOV
INT
POP
STI
DS
DX,KEEP IP
AX,KEEP CS
DS,AX
АН,25Н
AL,1CH
21H
DS
Язык Ассемблера в программировании информационных и управляющих систем 141
; В конце программы
;DS будет разрушен
;подготовка к восстановлению
;подготовка к восстановлению
;функция установки вектора
;номер вектора
;восстанавливаем вектор
;восстанавливаем DS
Имеется пара ловушек, которые следует избегать при написании
прерывания. Если новая процедура прерывания должна иметь доступ к данным, то
необходимо позаботиться, чтобы DS был правильно установлен, так как
обычно прерывание использует стек вызывающей программы. Другая неприятность
может заключаться в том, что при завершении программы по Ctrl-Break вектор
прерывания не будет восстановлен, если вы не предусмотрите, чтобы
программа реакции на Ctrl-Break выполняла эту процедуру.
Как отмечалось, TSR-программа служит для расширения MS DOS
средствами асинхронного взаимодействия между параллельными процессами.
Однако, ввиду того, что MS DOS изначально и насквозь однопрограммна,
включению TSR-программ сопутствует ряд проблем, таких как:
• анализ повторной инициализации TSR-программы;
• повторное использование нереентерабельной MS DOS в TSR-программах,
кроме того, необходимо использовать общепринятые приемы для
организации входа и выхода в TSR-программу и ее включения в MS DOS.
Хотя и не часто, но иногда бывает полезно добавить код к
существующему прерыванию. В качестве примера рассмотрим программы, которые
преобразуют одно нажатие клавиши в длинные определяемые пользователем
символьные строки (макроопределения клавиатуры). Эти программы используют
факт, что весь ввод с клавиатуры поступает через функцию 0 прерывания 16Н
BIOS. Все прерывания ввода с клавиатуры DOS вызывают прерывание BIOS
для получения символа из буфера клавиатуры. Поэтому необходимо
модифицировать лишь прерывание 16Н таким образом, чтобы оно служило
шлагбаумом для макроопределений, после чего любая программа будет получать
макроопределения, независимо от того, какое прерывание ввода с клавиатуры она
использует.
Конечно, модифицировать прерывания BIOS и DOS непросто,
поскольку BIOS расположена в ПЗУ, a DOS поставляется без листинга и они
ограничены размерами отведенной для них памяти. Но вы можете написать процедуру,
которая предшествует и/или следует за соответствующим прерыванием, и эта
процедура может вызываться при вызове прерывания DOS или BIOS.
Например, для прерывания 16Н нужно написать процедуру и указать на нее вектором
прерывания с номером 16Н. Начальное значение вектора 16Н тем временем
переносится в какой-либо неиспользуемый вектор, скажем, 60Н. Новая процедура
просто вызывает прерывание 60Н, чтобы использовать оригинальное прерыва-
I 42 Глава 6 Использование системы прерываний и программирование ввода-вывода
ние 16Н. Поэтому, когда программа вызывает прерывание 16Н, управление
передается пользовательской процедуре, которая затем вызывает оригинальное
прерывание 16Н. По завершении этой процедуры управление опять
возвращается через пользовательскую процедуру в то место программы, из которого
произошел вызов прерывания 16Н. После того как это сделано, в новой
процедуре может содержаться любой код как до, так и после вызова прерывания
60Н. Вот краткая сводка необходимых действий.
1. Создать новую процедуру, вызывающую прерывание 60Н.
2. Перенести вектор прерывания для 16Н в 60Н.
3. Изменить вектор 16Н, чтобы он указывал на новую процедуру.
4. Завершить программу, оставляя ее резидентной.
Следует сказать, что "стандартное" использование зарезервированного
вектора из набора 60Н-67Н не всегда защищает вас от повторной
инициализации TSR-программы путем проверки длинного адреса в векторе 60Н на
совпадение с точкой входа в обработчик. Этот "стандартный" прием может
использоваться другими TSR-программами, запущенными после вашей, даже если
они объединяют векторы 60Н-67Н в цепочку. Кроме того, вектор 67Н уже
фактически "монополизирован" драйверами памяти типа expanded. Для анализа
повторного запуска используйте функцию 27Н с указателем на персональную
строку или структуру.
Для связи с цепочкой характерны два случая:
а) ваша программа выполняется перед стандартным обработчиком,
тогда она должна завершаться командой длинного косвенного перехода:
JMP DWORD PTR [КЕЕР__1Р]
б) часть вашей программы выполняется после стандартного
обработчика, тогда обращение к нему может быть организовано так:
PUSHF
POP AX
AND АН,11111100В ; Сброс TF и IF
PUSH AX
CALL DWORD PTR [KEEP_IP] ; Обращение к стандартному
; обработчику
Краткие итоги
Сведения этой главы полезны при составлении оригинальных программ
ввода-вывода, обработчиков прерываний и резидентных программ для систем
типа MS DOS. Рассмотренные основы построения и эксплуатации
синхронизирующих примитивов могут применяться для организации различных видов
параллельного управления оборудованием ВК.
ГЛАВА 7
ПРОГРАММИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ
МАТЕМАТИЧЕСКОГО СОПРОЦЕССОРА
В данном разделе рассмотрены особенности архитектуры
(fx>pMamo(i данных и принципы программной эксплуатации
базовых математических сопроцессоров, а также методика про-
граммиуювания стцюцессора с использованием операций
вычисления частичных математических функции.
7.1. Архитектура и типы данных
математического сопроцессора
Математический сопроцессор является важной составной частью ВС,
совместимых с IBM/PC и для построения функционально полных систем
программирования необходимо владеть методами его непосредственного
программирования. Аппаратно сопроцессор реализуется либо в виде отдельной
микросхемы, либо начиная с i486 во встроенной модификации с сохранением всех
организационных и структурных особенностей. В управляющих системах данные
такого типа не являются определяющими, однако они обеспечивают
эффективность вычислений в разнообразных режимах. При реализации арифметики с
плавающей точкой в языках высокого уровня библиотечными средствами
используются разные режимы выполнения арифметических операций: работа с
сопроцессором, эмуляция и альтернативная арифметика. Использование того
или иного класса стандартных или библиотечных подпрограмм в языках
программирования высокого уровня определяется при инсталляции компилятора
языка в соответствии с имеющейся в наличии аппаратурой системы.
Рассмотрим подробно непосредственное программирование сопроцессора на языке
Ассемблера и эмуляцию программ сопроцессора на центральном процессоре.
Технология сопроцессоров, то есть процессоров, одновременно
подключенных к одной общей шине с центральным процессором, разделяемой во
времени, берет начало с канальных процессоров информационного обмена. По
сравнению с технологией связей с дополнительным оборудованием через порты
или двухпортовую память она имеет преимущество, позволяющее устранить
непроизводительные перемещения данных в начале и в конце решения задачи
на дополнительном оборудовании.
Архитектуру сопроцессоров типа ix87 по аналогии с центральным
процессором составляют накапливающие или запоминающие ресурсы для
оперативного хранения данных; операционные ресурсы, объединяющие команды
сопроцессора; управляющие ресурсы, которые охватывают средства командного
взаимодействия центрального процессора и сопроцессора, включая доступные
программисту регистры управления и состояния; а также коммуникационные
1 44 Глава 7 Программирование с использованием математического сопроцессора
ресурсы, обеспечивающие переключение элементов оборудования ВС во
времени, из которых программисту доступны только входы прерываний.
Восемь 80-битовых численных регистров с плавающей точкой,
скомпонованных в стек, элементы которого обозначаются в языке Ассемблера как
^[относительный номер регистра] вместе с общей главной памятью и для
главного процессора, составляют информационные или запоминающие ресурсы. В
большинстве версий Ассемблера номер регистра можно задавать как в
обычных, так и в квадратных скобках. Все арифметические данные в сопроцессоре
хранятся исключительно в 80-битовом формате, хотя в памяти могут храниться
данные еще и в четырех- и восьмибайтном форматах. Внешние форматы
данных сопроцессора могут быть целыми и с плавающей точкой: короткие - типа
DWORD, длинные - типа QWORD и десятибайтные - типа TBYTE, причем
целые данные имеют знак и представляются в дополнительном коде, а в десяти-
байтном формате они представляются только в двоично-десятичной форме с
нулями на месте порядка.
Данные типа, получившего в языке С название long double, которые
занимают десять байтов и состоят из знака s, положительной мантиссы m и
смещенного порядка с. Порядок определяется по общей формуле для всех типов
данных с плавающей точкой с = 2п-1-1+ /?, где п - количество разрядов для
представления порядка, р - значение двоичного порядка для представления
мантиссы числа с единицей в целой части. Более наглядным может быть
определение значения смещения порядка всеми двоичными единицами, кроме
старшего разряда. Разрядная сетка числовых регистров сопроцессора имеет
параметры п - 15, с - 16367 и может быть представлена в виде:
регистровый
S ССССССС
79
формат
72
71
сссссссс
64
m.mm .
63 . .
. . т
8
long double
mmmmmmmm
7
0
В языке Ассемблера данные такого типа определяются директивой dt.
Важно отметить, что внутренняя форма представления данных с плавающей
точкой в памяти хранится так, что последовательность размещения данных в
главной памяти, как и для других форматов данных соответствует принципу -
по меньшему адресу записывается менее весомый байт. Так данные в памяти,
сформированные по директиве
_longDoubleArg dt 23.23;
кодируются десятком байтов - 40 03 D9 D7 0А 3D 70 A3 D7 0А, который
размещается в памяти в обратной последовательности.
Рациональное использование разрядной сетки, заложенное в
сопроцессоре обеспечивает возможность не только представлять данные в наиболее
широком диапазоне, но и использовать специальные значения данных, сведенные
в табл. 7.1. Такие данные могут возникать вследствие переполнений и антипе-
Язык Ассемблера в программировании информационных и управляющих систем 1 45
реполнении или потери значимости результатов операций сопроцессора.
Большинство этих значений, за исключением неопределенности и нечисленных
данных, могут использоваться в вычислениях вместе с обычными значениями. Так,
например, действия с бесконечностью выполняются по правилам вычислений
бесконечных пределов. Использование неопределенности и прочих
нечисленных значений в вычислениях приводит к формированию неопределенных
результатов.
Таблица 7.1
Тип данных
Денормализован-
ные
Ненормализованные
+ 0
-0
+ 00
- 00
Неопределенность
Нечисленные
Характеристики
с=0000, назначение
c=7FFF, m=0.XX...X
с=0000, s=0, m=0
с=0000, s=l,m=0
c=7FFF, s=0, m=0
c=7FFF,s=l,m=0
c=7FFF,m=1.10..0
c=7FFF,m=l.lX...X
Применение значений
Минимальные с плавающей
точкой
Специальные с фиксированной
точкой
Промежуточные с плавающей
точкой
Нулевые данные и результаты
Нулевые результаты
Результаты переполнений
Результаты переполнений
Результаты особых случаев
Специальные данные пользователя
В сопроцессоре различают проективный и афинный режимы обработки
специальных значений. В проективном режиме константы +0 и -0, а также + °°
и - °° не различаются и сравнение числа с бесконечностью приводит к
недействительной операции, а в афинном - указанные пары значений различаются и
возможны сравнения обычных значений с бесконечностью. В сопроцессорах,
начиная со встроенного i486, проективный режим исключен.
Данные типа, называемого в языке С float, а в языке Pascal - Single и
занимающего четыре байта и п = 8, а с = 127 и имеют нормализованное
представление с размещением точки, отделяющей целую часть мантиссы от дробной,
после старшего двоичного значащего разряда. Единичная целая часть
мантиссы в памяти не записывается:
меньшие адреса
float
mmmmmmmm
шшшшшшшгп
с. mmmrnmmm
s ссссссс
7 0 15
Пример:
_floatArg dd 23.23;
8 23
16 31
24
146 Глава 7 Программирование с использованием математического сопроцессора
Значение константы в шестнадцатиричном представлении имеет вид: 41
Ь9 d7 Oa, при загрузке в память байты размещаются в обратном порядке.
Данные типа double занимают восемь байтов и имеют п = 11, с = 1023 и
представляются в памяти следующим образом:
меньшие адреса
mmmmmmmm
сссс .mmmm
double
s ccccccc
7 0 15 41 55 48 63 56
Пример:
_doubleArg dq 23.23;
Значение константы в шестнадцатиричном представлении имеет вид: 40
37 За el 47 ае 14 7Ь, при загрузке в память байты размещаются в обратном
порядке.
7.2. Базовые команды математического сопроцессора
Регистры сопроцессора доступны для программиста через номер,
определенный относительно текущей верхушки стека регистров. В языке
Ассемблера есть специальная группа команд, которая генерирует коды сопроцессора и
организует его взаимодействие с главным процессором. Названия этих команд
начинаются с буквы F, а следующие буквы определяют тип операции. Все
команды пересылок данных в процессор с необходимыми преобразованиями
данных имеют в названии буквы LD и выполняются путем размещения данных с
плавающей точкой в верхушки стека с предварительным изменением текущего
значения указателя стека st и преобразованием во внутреннюю десятибайтную
форму с плавающей точкой. Базовая команда FLD перемещает данные как из
регистров с плавающей точкой, указанных в командах Ассемблера в форме
st|jl, где i - номер нужного регистра относительно верхушки стека, так и с любой
области главной памяти и задается по формату:
1*ттка:] fid олёранд^источяяк
Команды FILD и FBLD задаются программистом для преобразования
целочисленных данных в форму с плавающей точкой при загрузке четырех-,
восьми- и десятибайтных данных в сопроцессор из памяти. Буква I задает
целочисленные двоичные данные, буква В - десятибайтные двоично-десятичные
данные в bed-коде. Команды FST и FIST задают копирование содержимого
верхушки стека st[O] с преобразованиями в формат, соответствующий
определению области приемника. Команды извлечения из стека и преобразования
данных: FSTP, FISTP и FBSTP после преобразования данных освобождают
соответствующий регистр стека и смещают указатель стека, причем операндом в
Язык Ассемблера в программировании информационных и управляющих систем I 4 ш
них может быть регистр сопроцессора. Команда FXCH задает обмен регистра
верхушки стека st[O] с регистром, заданным в операнде. Группа безоперандных
команд используется для загрузки распространенных констант в верхушку
стека FLDZ - О, FLD1 - 1, FLDPI - те, FLDLG2 - Ig2, FLDLN2 - In2, FLDL2T -
Iog210 логарифм числа 10 по основанию 2, FLDL2E - logze.
Аргументы стандартных математических функций языка С могут быть
переданы в подпрограмму через верхушку стека процессора в виде числа с
плавающей точкой в формате длинного вещественного представления (float
double), для чего в вызывающей последовательности для одноместной функции
можно использовать следующие команды, включая команды сопроцессора:
FLD х ; Загрузка аргумента в стек сопроцессора
SUB sp,8 ; Подготовка места в стеке главного процессора
MOV bp,sp ; Подготовка указателя
FSTP qword ptr[bp] ; Пересылка аргумента в стек из
;сопроцессора
CALL имя входной точки функции в библиотечном модуле
ADD sp,8 ; Освобождение стека
Результат вычисления стандартной функции также в формате длинного
вещественного (float double) будет размещен в сегменте данных, а его
внутрисегментный адрес в подпрограммах используемой библиотеки возвращается
при выходе из подпрограммы в аккумуляторе АХ. Таким образом, для приема
и сохранения результата можно использовать такие команды:
MOV bx,ax ; Копирование внутрисегментного адреса
FLD qword ptrfbx] ; Пересылка результата в сопроцессор
FSTP имя области приемника результата
На первый взгляд может показаться, что такая динамически изменяемая
нумерация регистров может вызвать серьезные осложнения при
программировании. Но большинство вычислений над действительными числами
выполняются по алгебраическим формулам, которые достаточно легко
программируются с использованием стека из регистров с динамически изменяемыми
номерами. Вычисления над данными, динамически занесенными в стек приводят к
тому, что одинаковые комплексы действий программируются одинаковыми
командами с одинаковыми регистровыми операндами.
Таблица 7.2
Команда
Fxxx память
FIxxx память
Fxxx st,st[i]
Fxxx st[i],st
FxxxP st[i],st
1-й операнд
верхушка стека
верхушка стека
верхушка стека
регистр стека
регистр стека
2-й операнд
операнд из команды
целый операнд из
команды
регистр стека
верхушка стека
верхушка стека
Стек
без изменений
без изменений
без изменений
без изменений
удаление из
верхушки
■ 48 Глава 7 Программирование с использованием математического сопроцессора
Команды арифметических операций включают 5 базовых форматов
команд, сохраняющих результат в первом операнде.
Безоперандная команда Fxxx реализуется как FxxxP st[i],st. Здесь ххх -
означает содержание операции из следующего списка: ADD - сложение; SUB -
вычитание второго аргумента из первого; MUL - умножение; DIV - деление
первого аргумента на второй; SUBR - реверсивное вычитание (первого
аргумента из второго); DIVR - реверсивное деление.
Если промежуточные результаты предыдущих вычислений помещены в
области памяти f 1 и f2, то фрагмент программы для суммирования имеет вид:
FLD £1 ; Загрузка первого слагаемого
FLD £2 ; Загрузка второго слагаемого
F&pD ; Суммирование с освобождение стека сопроцессора
FSTP result ; Запоминание результата в памяти
Для организации разветвленных и циклических программ нужно
использовать управляющие ресурсы сопроцессора, к которым относятся
16-битовые регистры состояния SW и управления сопроцессором CW. В состав
регистра состояния входят:
15
bfsssfffm
XX ееееее
О
где b - бит занятости, который принимает значения "1" на период выполнения
операции и выдачи прерывания; sss - указатель стека регистров с плавающей
точкой; ffif- четыре бита признаков результата и шесть битов ееееее , каждый из
которых, начиная с младших разрядов отображает особую ситуацию или исключение
(exeption): IE - недействительная операция; DE - ненормализованный операнд; ZE -
деление на 0; ОЕ - переполнение; UE - антипереполнение; РЕ - неточный результат;
где е - обобщенный бит ошибки, принимающий значение " 1" при наличии
незамаскированной особой ситуации. Признаки DE, UE и РЕ не приводят к
каким-либо существенным информационным потерям и могут быть
заблокированы практически без всякого ущерба результату, что делается установкой в "1"
соответствующих разрядов маски mmmmmm управляющего регистра CW:
15 0
XXX i rr pp
XX mmmmmm
где rr - определяет режим округления RC = 00 - округление к ближнему
значению (устанавливается по умолчанию); 01 - в сторону отрицательной
бесконечности; 10 - в сторону положительной бесконечности; 11 - в сторону нуля; рр -
режим управления точностью внутренних представлений: PC = 00 - короткий
4-байтовый формат; 10 - длинный 8-байтовый формат; 11 - внутренний
10-байтовый формат, принимаемый по умолчанию; i - режим интерпретации беско-
Язык Ассемблера в программировании информационных и управляющих систем I 49
нечности 1С=0 - проективный режим, а 1С=1 - афинный режим, в котором
исключительно функционируют сопроцессоры, начиная с i387.
7.3. Административные команды
Экспериментировать с простейшими действиями сопроцессора можно
опираясь на факт его начального сброса при загрузке системы, но для
построения системных управляющих программ необходимо владеть комплексом
административных команд для гибкого и полного управления сопроцессором.
Перед началом работы с математическим сопроцессором ix87 должна
быть выполнена инициализация сопроцессора. Программы установки среды
выполняют ее в числе других начальных установок. Если такие программы не
выполняются, то программист может задать начальные установки следующей
последовательностью команд:
FINIT ; Начальный сброс сопроцессора
FLDCW слово с начальным, состоянием регистра управления
Кроме начальных установок программист должен позаботиться об
обработке прерываний и переполнений стека сопроцессора, что может возникать
при вложенных обращениях к процедурам, использующим сопроцессор. К этим
командам относятся:
FSTCW слово-приемник регистра управления
FLDCW слово-источник регистра управления
FSTSW слово-приемник регистра состояния
FLDSW слово-источник регистра состояния
FSTENV блок-приемник состояния среды сопроцессора
FLDENV блок-источник состояния среды сопроцессора
FSAVE блок-приемник состояния среды и регистров сопроцессора
FRSTOR блок-источник состояния среды и регистров сопроцессора
FCLEX ; Сброс флагов особых случаев
Размер блоков сохранения должен обеспечить сохранение и
последующее восстановление как всех программно доступных регистров сопроцессора,
так и внутренних регистров сопроцессора, недоступных по другим командам:
• регистр управления CW (смещение 0 для 16- и 32-битовых режимов);
• регистр состояния SW (смещение 2 для 16- и 4 - для 32-битовых
режимов);
• слово тэгов, в котором каждая пара битов отображает текущее
состояние физического регистра с плавающей точкой двоичными кодами: 00 -
допустимое ненулевое число, 01 - нуль, 10 - специальное значений, 11 -
незаполненный регистр (смещение 4/8);
• указатель команды IP (6 / OCh);
• селектор CS и/или код операции (8 / 10h);
Глава 7 Программирование с использованием математического сопроцессора
• смещение операнда IP (Oah / 14h);
• селектор операнда (OCh / 18h).
Таким образом, для 16-битового режима полный размер области
сохранения составляет 94 байта, а 32-битового - 108 байтов. Отметим, что пустые
регистры в стеке сопроцессора могут возникать после выполнения команд
декремента стека сопроцессора FDECSTP и освобождения регистров FFREE st[/j.
Для составления связующих элементов программ и контроля за
взаимодействием сопроцессора с центральным процессором целесообразно
использовать элементарные команды математического сопроцессора, включенные в
язык Ассемблера. Основу взаимодействия составляет использование
центральным процессором и сопроцессором общей последовательности команд. В
принципе для управления математическим сопроцессором и взаимодействия с
центральным процессором используются команды центрального процессора ESC
и WAIT. Единственная команда, подключающая сопроцессор к работе имеет
мнемонику ESC представляется во внутримашинном двоичном формате:
15 0 displacement
1
(d8/d!6) i ...
11011 ссс
mod ссс г/т
Здесь буквами ссс обозначены биты кода операции сопроцессора, mod -
модификация операндов, г/т - регистр или указатель типа операнда в памяти. В
машинной форме после основной части команды с указателями модификаций
адресов в памяти могут находиться двухбайтный адрес для прямой адресации,
однобайтное d8 или двухбайтное dl6 смещение. Регистровые команды
сопроцессора имеют сокращенные форматы.
При получении команды ESC сопроцессор с помощью центрального
процессора формирует необходимые адреса памяти и начинает выполнение
своей операции, не препятствуя центральному процессору продолжать
вычисления по своей программе. Если нам в дальнейшей работе требуются
результаты сопроцессора, то для подтверждения завершения предыдущей операции
следует воспользоваться командой WAIT, которая переводит центральный
процессор в режим ожидания аппаратного сигнала завершения операции в
сопроцессоре и задается в безоперандном формате. Почти для всех команд
сопроцессора, записанных на Ассемблере в форме мнемоник начинающихся с буквы F,
генерируется код, начинающийся с кода операции WAIT. Исключение
составляют команды, начинающиеся с букв FN и не проверяющие особых случаев в
команде, например:
FNINIT ; Начальный сброс без контроля особых случаев
FNCLEX ; Сброс флагов особых случаев без их контроля
FNSAVE блок—приемник состояния среда и регистров сопроцессора
FttSTCW слово-приемник регистра управления
FNSTSW слово-приемник регистра состояния
Язык Ассемблера в программировании информационных и управляющих систем 151
7.4. Циклы и ветвления при работе с сопроцессором
Простейшая организация циклов при работе с сопроцессором и
известном количестве повторений состоит в использовании для организации циклов
исключительно команд центрального процессора с использованием
механизмов, рассмотренных в предыдущих главах.
Для организации ветвлений и проверки условий окончания циклов
используются группа команд сравнения сопроцессора и команды передачи слова
состояния в центральный процессор. FCOM - сравнивает st[O] с операндом, а
FICOM - с целым операндом из памяти; FCOMP - сравнивает содержимое st[O]
с операндом, после чего исключает его из стека сопроцессора, a FICOMP
делает то же действие при сравнении с целочисленным операндом; FCOMPP -
сравнивает два верхних регистра стека st[O] и st[l] с последующим их исключением
из стека;
FTST - сравнивает st[O] с 0. Перечисленные команды меняют
содержимое регистра состояния в соответствии с результатом вычитания второго
операнда из первого и могут быть использованы для организации условной
передачи управления по арифметическим отношениям.
С учетом особенностей структуры регистра F ЦП и регистра состояния
математического сопроцессора, а также наличием команды сопроцессора
FSTSW wrd и центрального процессора. SAHF можно организовать передачу
флагов сопроцессора в ЦП.
При программировании условных переходов по результатам работы
сопроцессора нужно учитывать, что в режиме эмуляции команды транслируются
в модифицированную форму, в которой сохраняется изоморфность к сопроцес-
сорной программе по количеству байтов, а значит и размещению команд, а
также сохраняются биты, в которых определены операции сопроцессора. При
этом код операции WAIT заменяется на INT с указателем определенной
группы уровней прерываний. На листинге эти преобразования не отображаются.
Для построения унифицированной программы, пригодной для выполнения на
сопроцессоре, и в режиме эмуляции необходимо использовать команду FWAIT
вместо WAIT.
Условные переходы по результатам сравнений в сопроцессоре можно
организовать макроопределением следующего вида:
FJ MACRO cd,lb ; Прототип макровызова
FSTSW stsw ; Запись в память регистра состояния
; сопроцессора и команды центрального процессора
FWAIT ; Ожидание окончания пересылки
MOV ah, byte ptr stsw-fl ; Копирование регистра состояния
SAHF ; Сохранение содержимого регистра ah в регистре F
J&CD lb ; Условный переход
ENDM
Для программирования условного перехода по результату сравнения
программисту достаточно использовать макровызов вида:
I 52 Глава 7 Программирование с использованием математического сопроцессора
[Метка:] fj Условие перехода, Метка перехода
Первый операнд макрокоманды, определяет условие перехода теми же
буквами, которые используются в командах условных переходов по
результатам беззнаковой арифметики, то есть а - больше, b - меньше, п - отрицание и е
- равенство. Команда FXAM позволяет получить гораздо больше информации
о содержимом st[O], но дает особое кодирование битов признака результатов, и
может использоваться также для инициализации кодов условия.
Таким образом, синхронизация параллельной работы центрального
процессора и математического сопроцессора реализуется достаточно просто и
в большинстве случаев прозрачно для программиста. Другой случай
необходимости синхронизации связан с возникновением особых ситуаций в
сопроцессоре. Сопроцессор при незамаскированной особой ситуации формирует сигнал
прерывания к центральному процессору по одному из трех номеров векторов
прерываний 07h - при отсутствии сопроцессора и 1 Oh или 75h - при ошибках и
особых случаях в сопроцессоре, в зависимости от структуры аппаратной части.
Для рационального программирования вычислений по формулам
разложения в ряд и рекурсивным формулам следует придерживаться следующих
рекомендаций по построению программ. После того как четко определена
предметная область, ее форматы данных и цели составляемых программ в виде
явных аналитических выражений, необходимо выполнить предварительные
эквивалентные аналитические преобразования, сокращающие, а в пределе и
исключающие выполнение повторных вычислений в программе. Эти действия по
своему существу обеспечивают машинно-независимую оптимизацию программ.
После этого для получения рациональной программы необходимо выполнить
эффективное распределение запоминающих и операционных ресурсов, в случае
языка Ассемблера семантически совпадающих с ресурсами центрального
процессора и математического сопроцессора. Для рационального использования
запоминающих и операционных ресурсов вычислительной системы
необходимо соблюдать следующие принципы:
• минимизация числа пересылок, для чего данные целесообразно
размещать в хранилище, используемом при выполнении требуемых операций;
• при большом объеме промежуточных вычислений предпочтением для
сохранения в регистрах пользуются повторно используемые данные, а
при недостатке регистров, редко используемые данные
предпочтительнее сохранять в памяти.
При программировании вычисления математических функций могут
применяться два подхода: вычисления по приближенным итеративным
формулам, использующим элементарные математические операции, и вычисления,
основанные на специальных операциях сопроцессора, вычисляющих частичные
значения трансцендентных функций.
В качестве базового примера рассмотрим процесс составления
программы в соответствии с формулой разложения в ряд Маклорена. При прямой
реализации формулы вычисления ряда в процессоре, имеющем операции сложения
и умножения, источником избыточных вычислений являются многократные
Язык Ассемблера в программировании информационных и управляющих систем 1 53
умножения при вычислении степеней х. Простейший способ их устранения
основан на аналитическом определении отношения последующего и
предыдущего членов ряда.
Построим пример реализации программы для экспоненциальной
функции ех, вычисляемой по формуле:
со /
ех = 2.— » гДе 0! = I • В этом случае отношение Ж-го члена ряда си+\ к i -му си
/=0
имеет вид: R = си+\ I at = х IL
С точки зрения распределения регистров сопроцессора при циклическом
выполнении вычислений в верхушке стека целесообразно хранить значение
очередного члена ряда, так как оно может использоваться для накопления
суммы в регистре из глубины стека и для их обновления путем умножения на хх и
манипуляции над числами, связанными с 2/. Эти числа удобнее всего строить,
основываясь на хранении 2/+1 в целой форме с последующими инкрементами в
оперативной памяти. Процедура вычисления функции интегрального синуса,
составленная с учетом соглашений о связях языка С, может быть представлена
в виде:
_Ехр PROC
PUSH BP ; Стандартное сохранение базового указателя стека
MOV BP,SP ; Установка нового значения базового указателя
FLD QWORD PTR[BP+4] ; Загрузка начального значения суммы к
FLD st ; Загрузка значения 11 0-го члена ряда.
FLD st ; Загрузка к для вычисления постоянного множителя
MOV cntr,l
LSi:FMUL st[l]
FIMUL cntr
INC cntr
FIDIV cntr
FADD st[2],st
CMP cntr,10
JB LSi
FINCSTP
FINCSTP
FSTP result
MOV AX,offset DGROUP:result
POP BP
RET
JExp ENDP
Запись начального значения счетчика
Домножение на - к * к
Домножение на 21-1
Инкремент счетчика
Деление на 21+1
Накопление суммы членов ряда
Контроль предельного значения счетчика.
Переход, если расчет не закончен
Освобождение стека сопроцессора
Освобождение стека сопроцессора
Сохранение результата
Восстановление базового указателя стека
Выход из подпрограммы
Здесь cntr должно быть определено как WORD, result как QWORD в
сегменте данных. Команды FINCSTP и FDECSTP применяются для баланса
стека сопроцессора и изменения значения его указателя, добавляя или вычитая
единицу из указателя стека соответственно.
I 54 Глава 7 Программирование с использованием математического сопроцессора
7.5. Программирование сопроцессора с
использованием операций вычисления
частичных математических функций
Наиболее рациональным путем вычисления функций является
использование встроенных операций сопроцессора, вычисляющих полные или
частичные значения математических функций. Команды сопроцессора этой группы
можно разделить на две подгруппы, в первой из которых вычисляются
простейшие математические функции и выполняются функционально завершенные
вспомогательные операции сопроцессора. При описании команд декремент и
инкремент указателя стека предшествующие формированию результатов будем
обозначать в терминах языка С как - - st, ++ st.
FSQRT - заменяет аргумент в st[O] значением квадратного корня.
FABS - заменяет содержимое st[O] его абсолютным значением.
FCHS - смена знака содержимого st[O].
FRNDINT - округляет содержимое st[O] до целого значения.
FXTRACT - расщепляет содержимое st[O] на два числа: несмещенный
порядок в формате с плавающей точкой, заменяющий аргумент, и мантиссу со
знаком, дополнительно размещаемую в стеке после — st. Команда FSCALE
после FXTRACT восстанавливает начальное число, но не восстанавливает
указатель стека.
FSCALE - к значению порядка в st[O] добавляется целая часть числа из
st[l] и таким образом восстанавливаются исходные данные команды
FXTRACT, для компенсации изменений указателя стека нужно дополнительно
выполнить команду FSTP st[l].
FCOS [*3] - заменяет аргумент в st[O] значением синуса для аргумента в
радианах в пределах - 263.. 263.
FSIN [*3] - заменяет аргумент в st[O] значением синуса для аргумента в
радианах в пределах - 263.. 263;
FSINCOS [*3] - заменяет аргумент в st[O] значением синуса для
аргумента в радианах в пределах - 263.. 263, затем выполняет — st и помещает в новую
верхушку стека значения косинуса того же аргумента (выполняется быстрее
отдельных функций).
FPREM - вычисляет частичный остаток от деления st[O] на st[l] и
размещает его в st[O]. Знак st[O] не изменяется, а если для получения окончательного
результата достаточно меньше, чем 64 масштабированные вычитания, то
остаток, меньший модуля со знаком делимого, будет записан в st[O]. При этом три
младших бита частного, позволяющие определить квадрант аргумента при
делении на я/2 помещаются в разряды СЗ, С\ и СО регистра SW.
FPREM1 - то же, что и FPREM, но абсолютная величина остатка
меньше половины модуля в соответствии со стандартом IEEE-754.
Процессоры ix87 могут вычислить любую элементарную
трансцендентную функцию с аргументом, точность представления которого определяется
содержимым регистра управления, и последующей выдачей результата такой же
точности. В названиях команд и их описаниях принято обозначать аргументы,
Язык Ассемблера в программировании информационных и управляющих систем I 55
содержащиеся в st[O] как X, а в st[l] - как Y. Результаты, формирующиеся в st[O],
будем обозначать как х, а в st[l] - как у. Для упрощения восприятия этих
команд, представленных в табл. 7.3, следует учитывать, что они разработаны с
возможностью получения полного результата с помощью самой простой и
естественной дополнительной команды.
Вычисление тригонометрических функций основано на выполнении
команды FPTAN - нахождения частичного тангенса, которая в качестве
результата дает два таких числа х и у, что у I x = tg X. Число у заменяет старое
содержимое st[O], а число л* включается в стек дополнительно.
Таблица 7.3
Мнемоника команды
FPTAN
FPATAN
FYL2X
FYL2XP1
F2XM1
Название операции
Вычислительная формула
Частичный тангенс
--st,y/x -igX
Частичный арктангенс
++st, x = arctg(7/X)
Вычисление логарифма
++st, Y*\ogiX
Вычисление логарифма
++St, 7*l0g2(X+l)
Вычисление X =2Х -1
Ограничения на аргументы
0<= X <= тс/4 : 286
|Х| <2*з : 386
У>Х>1 :286
нет ограничений : 386
X >0
\х\<\-4212
0<=Х<= 0.5 :287
-1<=Х<=1 : 387
Вычисление тригонометрических функций первых математических
сопроцессоров было основано на выполнении команды FPTAN - нахождения
частичного тангенса, которая формирует результат в виде таких двух чисел х и
у , что у I х — igX. Число у заменяет старое содержимое st[O], а число л;
добавляется к стеку дополнительно. Для вычисления значений тригонометрических
функций сначала нужно отделить знак аргумента X [2] от значения; потом
поделить на период к для тангенса, если х >~ л/2, то изменить знак результата и
значение аргумента на х - /г, вычислить аргумент z = x /2 и использовать
формулы
sin z =
2у/х
\+(у/х)2
cos z =
tg z =
2у/х
1-(у/х)
Обобщенный алгоритм вычисления трансцендентных функций по
командам вычисления значений частичных функций выполняется как
последовательность таких блоков:
1. Приведение входных данных к допустимому диапазону значений и
запоминание обратного пути для восстановления результата в нужном диапазоне.
2. Вычисление частичной функции от приведенного значения аргумента
базовой командой сопроцессора.
3. Вычисление значения функции для требуемого значения аргумента
вспомогательными командами.
I 56 Глава 7 Программирование с использованием математического сопроцессора
Рассмотрим программирование вычисления тригонометрических
функций с использованием их выражений через тангенс половинного угла. Эти
формулы имеют неограниченный диапазон определения. Тангенс половинного
угла имеет относительно аргумента z период л/2 и если организовать изменение
знака, то достаточно использовать половинный аргумент.
При программировании тригонометрических функций диапазон
значений аргумента можно свести к допустимому командой FPREM или проверить с
помощью команды FXAM, так как он должен быть нормализованным и
находиться в диапазоне 0 < st[O] < л/4. Если аргумент X лежит вне этого диапазона,
то в начале программы необходимо выполнить преобразования аргумента к
нужному диапазону и запомнить данные для обратного преобразования.
Существуют несколько путей преобразования с использованием формул через
тангенс половинного угла и формул приведения значений тригонометрических
функций. Наиболее эффективны пути, нашедшие наиболее полное отражение в
системе команд сопроцессора.
Для функции \gX можно использовать 4 преобразования:
J<0; S =sign*; Y = \х\\ tgJT = StgY; (1)
в этом перечне сначала указано условие сведения для отрицательных
аргументов, затем две формулы для получения сведенных аргументов и, наконец,
формула для восстановления итогового результата.
Y>7T, Z-7-пл; tgr=tgZ; (2)
Здесь устраняется п - кратное повторение периода и дополнительных
действий по восстановлению результата не требуется. Следующие два
преобразования могут быть проведены в безусловной форме, так как
модифицированный аргумент лежит в диапазоне 0 £ У £ п.
U = Z/2; tgZ = 2tgl! ; (3)
1 - tg2U
V = U\2\ tg U = 2tg \ ; (4)
1 - tg2V
Эти формулы, основанные на двукратном применении известной в
математике универсальной тригонометрической подстановки через тангенс
половинного угла, легко реализуются, так как деление на 2 быстро осуществляется
командой FSCALE, и могут быть преобразованы с учетом того, что результат
вычисления функции FPTAN(K) сформирован в виде чисел х и у. Тогда
tg V- ХУ и и- 2ху ; v = х2-у2, (5)
х" -у1
где и I v = tg V.
Эти формулы можно рассматривать как базовые для расчета тангенса и
других тригонометрических функций с помощью команд FPTAN и FPREM,
используя таблицу формул приведения и следующие формулы, выраженные через
и и v.
Язык Ассемблера в программировании информационных и управляющих систем I 5 t
sinF =
2ц/v
1 - (« / v)
2 '
cos Y = = •
1 + (и / v)
2 '
(6)
cosec У = = ■
sec У = = ■
1 + (w / v)2
1 - (и / v)
2 *
Пример процедуры вычисления тангенса, построенной по
рассмотренным формулам:
_tg PROC
PUSH BP ; Стандартное сохранение базового указателя стека
MOV BP,SP ; Установка нового значения базового указателя
FLDPI ; Загрузка числа я
FLD QWGRD PTR[BP+4] ; Загрузка аргумента х
FTST
1 stsw
stsw
Исключение периода
Циклическое исключение остатка
Формирование - 2
Деление на 4
Вычисление составляющих tg
Дублирование числителя
Квадрат числителя у * У
Обмен на знаменатель
Вычисление х * у
Вычисление х * х
Получение -* v
Получение - и
Дублирование числителя и
Квадрат числителя и * и
Обмен на числитель
Вычисление и * у
Вычисление v * v
Получение - знаменателя tg
Получение - числителя tg
; Для этой команды нужно предусмотреть защиту
; от особых ситуаций
FDIVP st[ljfst ; Получение значения tg
FSTP result ; Сохранение результата
MOV AX,offset DGROUP:result
POP BP ; Восстановление базового указателя стека
FSTSW
PUSH
stsw
stsw
rm: FPREM
f j
FLD1
FCHS
FADD
FXCH
FSCALE
FPTAN
FLD
FMUL
FXCH
FMUL
FMUL
FSUBP
FMUL
FLD
FMUL
FXCH
FMUL
FMUL
FSUBP
FMULP
p, rm
st, st
st[l]
st[l]
st,st
St£l]
st [2]r
st,st
st[l]f
st
st
st,st[2]
st
st, st
st(2j
St[l],
st, st
st[21,
stf2b
st
st
st
I 58 Глава 7 Программирование с использованием математического сопроцессора
RET
_tg ENDP
; Выход из подпрограммы.
Кроме рассмотренного способа тригонометрические функции могут
вычисляться либо через тангенс половинного угла по формуле (3), либо через
тангенс полного угла по формулам:
cos х
ctg x = 1 / tg x. (7)
Однако эти формулы усложняют программу в части сведения углов и
восстановления результатов по сравнению с формулами, использующими
тангенс половинного угла.
Команда FPATAN вычисляет arctg(st[l]/st[O]) = arctg(7/J). Два верхних
элемента извлекаются из стека, а результат включается в стек. Операнды этой
команды должны удовлетворять условию 0 < Y < X, иначе необходимо
использовать формулы приведения:
arctg х - -arctg(- x), arctg x - я/2 - arctg (I/ x). (8)
Остальные обратные тригонометрические функции находятся с
помощью команд FPATAN, FSQRT и таких формул:
Z
= arctg(17Ar),
arcsin z = arctg
У = z, X =
arccos z = 2arctg VI - V = 2arctg( Y/ X),
Y = VT^z ,X - VJ77;
arctg z = arctg(T/ X)\
arcctg z = arctg(l/ z ) = arctgCY/ Y)\
arccosec z =
=axctg(Y/ X),
arcsec z = 2arctg д/z2 -1 = 2arctg( Y/ X),
Y= Jz2-\ , ДГ= 1.
В этих формулах z - аргумент вычисляемой функции, X, Y - значения
составного аргумента в регистрах st[O], st[l] перед выполнением команды
FPATAN, результат которой помещается в вершину стека.
Формулы для вычисления логарифмических функций:
log2x => FYL2Xnpn Y =>FLD1, X = FLD л;
Язык Ассемблера в программировании информационных и управляющих систем I 59
In x => FYL2X при Y => FLDLN2, X => FLD x ;
lg x => FYL2X при Y => FLDLG2, X => FLD x .
Команда FYL2X вычисляет st(l)* Iog2(st[0]) при st[O] > 0. Оба операнда
извлекаются из стека, а затем результат помещается в стек. Команды FLDL2E,
FLDL2T загружают константы со значением двоичных логарифмов числа е и
десятки.
Еще одна логарифмическая команда FYL2XP1 вычисляет st[l](st[O]+l)
при st[O] < 1-1/v 2 и используется для вычисления обратных гиперболических
функций. Команда F2XM1 вычисляет 25/1°М, причем для аргументов должно
выполняться условие:
-1 <st[0]<l,a^w286 0 < st[O] < 0.5.
Формулы для вычисления обратных гиперболических функций
ориентированы на использование команд вычисления логарифмов, квадратного корня
и загрузки констант:
, , N
arsh х = sign x *ln 2* Iog2 (1 + z ), где z = | х |+ '
arch л- = In 2 * Iog2(l+z), где z = x-l+Vx2 - 1 и х>1;
arth х = sign х In 2 * log: (1+ z ), где z = 2| x |/(1-| x |) и -1 < x < 1.
Для интервалов вне допустимых значений аргументов используются
формулы приведения для обратных величин аргументов:
arcth х =arth I/ x ; arcsh x =arsh I/ x ; arsch x =arch I/ x.
Вычисление гиперболических и показательных функций организуется с
использованием команды F2XM1 на основе вычисления промежуточных
значений частичных экспоненциальных функций.
sh х = sign х ; ch х -0.5( е'*' +1 / е'*');
th x =sign x —r-j— ; cth x = 1/th x ; csh x = 1/sh x ;
schx = 1/chx .
Формулы для вычисления показательных функций:
2* = (2*-1)+1 =>F2XM1(jc)+1;
ех= 1 + 2х1°ё2е-1 => i+F2XMl(x*FLDL2E);
10'= i + 2xIog210-1 => l+F2XMl(x*FLDL2T);
хУ= i-f 2ylog2X-l => l+F2XMl(FYL2X(^,x)).
I uU Глава 7 Программирование с использованием математического сопроцессора
FLD1
FLD
FLD
FYL2X
x
У
FXTRACT
FXCH
FISTP
FIDIV
F2XM1
FADD
MOV
ADD
st[l]
tem
qrt
CX,2
CX,tem
ml:FMUL st,st[03
LOOP
ml
Таким образом, для реализации последней функции достаточно
выполнить такую последовательность команд:
Загрузка единицы
Загрузка основания
Загрузка показателя
Вычисление логарифма результата
Расщепление аргумента экспоненты
Сохранение порядка показателя
Сведение диапазона делением на 4
Вычисление показательной функции
Коррекция приведенной экспоненты
Корректирующая добавка к порядку
Определение числа возведений в квадрат
Возведение в квадрат
Циклическое повторение
Теоретически конечный цикл может повторяться до 16000 раз, но
реально это возможно только когда основание близко к единице, так как в
противном случае гораздо раньше наступит переполнение или потеря значимости.
В данном разделе рассмотрены особенности архитектуры форматов
данных и принципы программной эксплуатации базовых математических
сопроцессоров, а также методика программирования сопроцессора с
использованием операций вычисления частичных математических функций.
Краткие итоги
Хотя сопроцессор спроектирован для более эффективного
программирования с языков высокого уровня, разработка сопроцессорных реализаций
описанных библиотечных функций может выполняться средствами Ассемблера.
Это позволяет несколько повысить скорость выполнения программ
сопроцессора.
ГЛАВА 8
РАБОТА С ВНУТРЕННИМИ СТРУКТУРАМИ
ДАННЫХ ИНФОРМАЦИОННЫХ СИСТЕМ
В топ главе рассмотрены основные методы поиска инс/юрма-
цни в иш/юрмационных и управпяющил системах, приходные
также дчя системных программ общего назначения
Рассмотрены пути обобщения методов гюйс ка и с сопоставления данных,
создающие основу для развития методов управления
вычислениями в направлении обобщения методов решения задач искус-
с таенного интеллекта с применением
машинно-ориентированного инструмента языка Ассемблера
8.1. Структуры информационных таблиц и основные
функции для работы с ними
Основу кибернетических методов составляет абстрактная модель
объекта, которая может изменяться в процессе развития или жизнедеятельности
системы. С позиций решения задач в ВК в зависимости от характера объектов
могут использоваться аналитические (алгебраические), алгоритмические
(директивные), реляционные (табличные) и сетевые модели, а также их
разнообразные комбинации. В этой главе основной упор сделан на изучение техники
структурного проектирования таблиц и построения эффективных программ с
применением машинно-ориентированных средств программирования. При
таком подходе программы информационного поиска рассматриваются на общем
системном уровне реализации с абстрагированием от прагматики конкретного
применения. Поэтому за основу прагматических иллюстраций возьмем пример
задачи трансляции с входных языков информационных систем, элементы которой
рассматривались и в предыдущих главах.
Задачи информационного поиска в системных программах
соответствуют общим тенденциям информационного поиска в информационных системах
(ИС) и управляющих системах (УС) широкого применения. Решение этих задач
должно обеспечить по возможности быстрое установление или определение
связей относительно простого синтаксического и семантического соответствия
по разнообразным информационным структурам. Для решения таких задач
строится информационная база (ИБ), в которой накапливается первичная
информация и результаты предварительной обработки директивной информации.
Любые действия ИБ с позиций интеллектуальных информационных
систем (ИИС) можно определить как накопление информации или вычисление
необходимых характеристик информационных объектов для управления и
принятия решений по накопленной информации. С этих позиций любая структура
6 В.И. Пустоваров
I 62 Глава 8 Работа с внутренними структурами данных информационных систем
ИБ может рассматриваться как массив последовательно расположенных
элементов со связанной с нею разветвленной структурой указателей или ссылок.
В большинстве системных программ главной целью задачи поиска
является определение характеристик, связанных с символическим обозначением в
форме имени, идентификатора или другого символического обозначения,
которое рассматривается как аргумент поиска или ассоциативный признак
информации. Поиск по ассоциациям может выполняться как во входной оперативной
информации так и в ИБ, где сохраняются систематизированные аргументы
поиска и их характеристики, упорядоченные или систематизированные в таблицу.
В большинстве системных программ наиболее важны задачи формирования
таблиц и оперативного поиска информации в таблицах.
Особенности построения современной ИБ вырисовываются в типовых
примерах, которые отличаются раздельным хранением образов отдельных
характеристик объектов и их структурных связей. Такой же подход использован
для хранения картин и сцен в виде кодов, которые могут быть обобщены на
уровне языка, предназначенного для воспроизведения структур с
управляемыми типами образов.
Проверка синтаксической корректности и генерация кодов в
транслирующих программах требуют знаний характеристик идентификаторов, имен и
обозначений, которые используются в программе на входном языке. Эти
характеристики определяются из описаний объектов и использования имен в
программе, и накапливаются в таблице имен и обозначений. Аргументом или
ключом поиска является символьный образ имени или обозначения из
обрабатываемого текста, а характеристики определяются при предварительном анализе
программы. Чаще всего аргументом поиска в таблице является содержимое
какого-то естественного поля ИБ или нескольких ее полей. Однако иногда, с
целью ускорения поиска за счет дополнительной систематизации и упорядочения,
к ней добавляются специальные поля ключевых слов, или такие слова
включаются в упорядочивающую надстройку и в дальнейшем именно они часто
становятся аргументами поиска.
Другая сторона задачи поиска состоит в оценке меры близости
сравниваемых объектов. В том случае, если не удается получить полное совпадение
анализируемого аргумента поиска с соответствующей характеристикой или
комплексом характеристик элемента таблицы, то для многих задач будет
важным найти по возможности близкие варианты решений.
Рассмотрим способы организации, построения и поиска на примере
таблицы имен, как базовый вариант построения ИБ. Современные требования к
ИБ формулируются исходя из тенденций развития задач, решаемых на ЭВМ в
направлении интеллектуализации их использования. Если главное требование
к большинству программ состоит в надежности их выполнения, то выбор
алгоритма управления ИБ зависит от требований производительности,
предъявляемых практически к любой системной программе:
• возможность выполнения в удовлетворительное время с ограниченными
затратами машинных ресурсов;
• получение нужной информации для достижения цели основной
системной программы.
Язык Ассемблера в программировании информационных и управляющих систем I 63
Исходя из изложенного, работа с И Б при решении разных задач требует
реализации двух комплексов функций или объектно-ориентированных методов:
• комплекс сравнений, сопоставлений и контроля критериев и оценок
близости;
• комплекс манипуляций с таблицей и ее элементами;
• комплекс управления памятью для оперативного и долговременного
хранения таблиц.
Поскольку первый и третий комплекс могут быть представлены как
сложные самостоятельные проблемы, вырождающиеся во многих применениях
к простейшим вариантам, сначала более подробно остановимся на комплексе
методов манипуляций с таблицами. Будем считать имеющейся в наличии
требуемую функцию сравнения и возможность организации долговременного
хранения ИБ путем обычной перезаписи на дисковый накопитель. Эти комплексы
составляют объектно-ориентированные методы:
• создания или получения доступа к ИБ, выполняемые
функцией-конструктором;
• удаление ИБ, выполняемое функцией-деструктором;
• включение нового элемента в ИБ;
• поиск в ИБ одиночного элемента по определенным значениям
аргументов или функций сравнения;
• поиск в ИБ полного множества элементов, удовлетворяющего
заданному критерию;
• поиск в ИБ группы наиболее предпочтительных элементов,
удовлетворяющей заданному критерию;
• упорядочение или сортировка таблицы или ее сегмента;
• удаление элемента из ИБ;
• коррекция элемента в ИБ;
• удаление блока элементов.
Отметим, что первые два метода являются неотъемлемыми элементами
любой реализации объектно-ориентированного подхода.
Важной характеристикой комплекса манипуляций с таблицами является
допустимость или недопустимость дублирования в ИБ базовых аргументов.
При запрете дублирования может возникнуть целесообразность или'даже
необходимость переключения методов сравнения аргументов, относящихся к
другому комплексу методов. Как вспомогательные, но весьма удобные методы для
подобных случаев можно рассматривать методы поиска элемента со вставкой
при отсутствии в ИБ анализируемого аргумента или несовпадение его
характеристик.
Результатом поиска обычно есть элемент таблицы, ключ которого
совпадает с аргументом поиска в таблице. Но во многих случаях ИИС важный
смысл имеет поиск элементов, в соответствии с определенной заранее мерой
близости. Эти меры существенно отличаются в зависимости от цели такого
поиска. Так, для коррекции случайных ошибок оператора при подготовке текстов
6*
Глава 8 Работа с внутренними структурами данных информационных систем
и случайных сбоев в каналах связи как меру сходства следует использовать
минимум обобщенных вариантов оценки кодовых отношений между
сравниваемыми словами или символическими обозначениями. При поиске родственных
слов необходимо пользоваться мерою, опирающейся на такие их
морфологические характеристики, как корни, окончания, их размеры, правила
словообразования и др. При таких задачах простое упорядочение по одной характеристике
целесообразно только тогда, когда переменные характеристики одномерны,
или легко систематизируются по одному параметру. В противном случае
классификация даже в условиях малых случайных отклонений требует
чрезвычайных затрат, а классификация в условиях комплексных связей требует
специальных упорядочивающих структур.
Логически простейший способ построения ИБ состоит в определении
структуры отдельных элементов, встраиваемых в дальнейшем в структуру
таблицы. Элементы в общем случае состоят из полей двух типов - аргументы и
функциональные характеристики. Такая классификация весьма условна и
определяется целевыми характеристиками предметной области. Как уже отмечалось
в качестве аргумента поиска в общем случае может использоваться несколько
полей, но для упрощения задачи примем два допущения:
• аргумент поиска представляется одним полем и связанной с ним
функцией сравнения, что позволяет переложить трудности, связанные с
множественной и функциональной комбинированностью аргументов на
методы сравнения;
• анализируемый элемент или прототип поиска имеет ту же или близкую
структуру, что и элемент таблицы.
Эти допущения практически не снижают общности анализа и
получаемых в результате его алгоритмов, но существенно упрощают
программирование методов манипуляции с таблицами. Каждый элемент обычно имеет
несколько (т) характеристик и занимает в памяти последовательные байты. Если
элемент занимает к байтов и нужно сохранять N элементов, то необходимо
иметь, по крайней мере, kN байтов памяти. Разместить информацию можно
несколькими способами:
1. Все элементы разместить в kN последовательных байтах и построить
таблицу из N элементов в виде массива. Пример такой таблицы приведен в
четвертой главе, где элементы задаются структурой TB_ELEM, длиной к - 20
байтам.
2. Построить п таблиц в виде массивов, скажем, Ti, T2,... Tw, для каждой
из т характеристик. При этом z-й элемент таблицы будет распределен по
элементам массивов Тп, 72/,..., Т,ы. Интересно отметить, что при использовании
коротких аргументов поиска длиной 1, 2 и 4 (для 32 разрядного режима) байта
процесс обработки может быть существенно ускорен за счет использования
команд работы со строками.
3. Разделить таблицу на / блоков, сегментов или подтаблиц. В предельном
случае можно получить совокупность блоков, в которых хранится по одному
элементу. В состав такой таблицы необходимо добавить еще и связующие указатели в
Язык Ассемблера в программировании информационных и управляющих систем I DO
форме адресов связываемых элементов, и таким образом организовать связанные
списки.
Вопрос выбора лучшего метода организации таблиц связано с целью
использования и типом таблицы, сложностью построения программ,
эффективностью и эксплуатационными характеристиками построенных программ. Однако
для облегчения анализа и логического упрощения программы предпочтение
отдается методам группы 1 для построения сконцентрированных таблиц и
группы 3 - для распределенных таблиц и таблиц большого объема.
Приведен пример фрагмента программы поиска по простейшему
линейному алгоритму с последовательным сравнением аргумента поиска с
соответствующим полем распределенной ИБ второй группы. Будем считать, что
аргумент загружен в аккумулятор AL, АХ или ЕАХ, начальный адрес таблицы в -
ES:DI, а количество элементов таблицы в - СХ.
MOV BX,DI ; Копирование начального адреса таблицы
REPNZ SCAS ArgTab ; Сканирование - фиктивный операнд
; определяет размер элемента таблицы
JNZ NotFnd ; Переход, если элемент не найден
SUB DI,BX ; Определение индекса найденного элемента
SUB DI/SIZE ArgTab ; Компенсация технологического пропуска
MOV CL,NShft ; Количество сдвигов индекса
SLL DI,CL ; Определение индекса функциональной
; части таблицы, если ее размер больше
; размера аргумента поиска
Подобный вариант поиска удобен при работе с таблицами малой длины,
применяемых для анализа вхождений аргумента в интервалы аппроксимации в
автоматизированных системах измерения и управления технологических
процессов, а также для организации таблиц задач в управляющих программах ОС.
При построении компиляторов и информационных систем используют
разные типы таблиц, похожих по содержанию и назначению, которые могут
быть объединены в единую таблицу, так и принципиально разные, как,
например, таблица символических обозначений, используемая при преобразовании
входной программы во внутреннюю форму, и таблица выходных кодов,
предназначенная для трансформирования внутренней формы протранслированной
программы в объектную форму или объектный файл целевого компьютера.
Еще два варианта таблиц необходимы для эквивалентных аналитических
преобразований программы и оптимальных покрытий внутренней модели
программы элементарными ресурсами во время генерации оптимальных кодов. В
ИС в основу реляционных БД также положен табличный принцип. Таким
образом задачи манипуляций с таблицами, их блоками и элементами преследуют
программиста в любой подсистеме систем автоматизации программирования и
проектирования, информационных и управляющих системах и в ИИС.
В случае таблиц имен и символических обозначений аргументами
поиска будут имена или идентификаторы, а функциями - такие их характеристики,
как тип, блок определения, начальное (инициализируемое) значение и др. Если
количество букв в именах очень непостоянно, в поле аргумента часто вместо
Глава 8 Работа с внутренними структурами данных информационных систем
самого имени размещают указатель на него. Это обеспечивает фиксированный
размер поля аргумента, но требует дополнительного сохранения отдельного
сегмента образов аргументов, в котором строки букв образов имен
сохраняются по стандарту выбранного языка программирования. В начале трансляции
входной программы таблица имен пустая или вмещает несколько элементов
для служебных или ключевых слов и предопределенных имен стандартных
объектов и функций. В предварительной фазе компиляции для каждого нового
имени однократно формируется новый элемент, но поиск может выполняться
несколько раз, когда используется это имя. Такие процессы занимают
значительную часть времени работы системной программы, что вызывает необходимость
эффективной организации часто повторяемого поиска.
Для сравнения разнообразных методов работы с таблицами по времени
поиска будем определять количество п сравнений аргументов, необходимых
для выполнения поиска относительно общего количества элементов таблицы.
В реляционных системах управления базами данных (РСУБД) общего
назначения основу сохранения баз данных чаще всего составляет
последовательность элементов таблицы, построенных по методам группы 1 и
интегрированных в так называемый DBF-файл. Принципиальным отличием
реляционных баз данных от ИБ системных программ является то, что базовым
носителем DBF-файла есть внешний накопитель, а оперативная память используется
как буфер в процессе обработки блока записей. Базовым носителем таблицы -
является оперативная память с возможным использованием внешней памяти
лишь при недостаточном объеме оперативной.
Построение ИБ системных программ является сердцевиной системного
программирования потому, что дискретная основа и относительно неглубокая
аналитичность системных программ приводят к интенсивному применению
табличных методов воспроизведения и определения функций. Важные ИБ
встраиваются в системные программы практически всех классов. Выбор
рациональных решений осложняется широким разнообразием технических решений
при построении ИБ и многочисленностью направлений их классификации. Для
систематического подхода рассмотрим сначала требования и
классификационные характеристики ИБ. В соответствии с требованиями и критериями
построения ИБ системных программ они должны обеспечивать:
• декомпозицию объектов ИБ на блоки и сегменты желательно выполнять
по семантическим и прагматическим признакам, т.е. блоки или сегменты
должны, по возможности, объединять однородную и однотипную
информацию, признаки которой определяются проблемными областями
использования результатов вычислений;
• использование внутренних кодов, благоприятных для сохранения и
обработки данных при решении задач;
• раздельное размещение в блоках внутренних связей и внешних
информационных образов как семантически различных объектов;
• предельно простое и быстрое по доступу воспроизведение указателей с
возможностью связи с семантической информацией для построения
упорядочивающих и систематизирующих структур;
Язык Ассемблера в программировании информационных и управляющих систем 1 67
• размеры информационных блоков должны обеспечивать предельно
быстрый доступ к структурам данных больших размеров;
• для индексированных файлов формирование
синтаксически-ориентированных упорядочивающих или индексных структур современными
скоростными методами и с быстрой обработкой переполнений
информационных и индексных блоков;
• сокращение избыточности информации при построении моделей
базовых проблемных областей и соответствующих информационных
структур для сходных проблемных областей;
• желательна возможность выполнения коррекции информации в
фоновом режиме наряду с выполнением других вычислений;
• изменяемые элементы должны быть функционально достижимыми сразу
после ввода или подтверждения изменений.
Построение новых средств искусственного интеллекта и развитие
разнообразных систем автоматизации управления и ИС вызвала необходимость
нового анализа и обобщения задач информационного поиска и преобразований с
ракурсов, открывшихся при решении новых задач. Традиционный подход к
решению таких задач основан на построении разнообразных таблиц, и играет
значительную роль в современных ИС, но становится тормозом развития при
переходе к задачам поиска информационных объектов в сложных информационных
структурах, таких, например, как поиск текстовых фрагментов по содержанию,
заданному текстовыми логически связанными шаблонами, поиск образов в сложных
картинах, сценах или их проекциях.
8.2. Базовые методы и алгоритмы поиска, упорядочения и
сортировки в информационных таблицах
Логически наиболее простым способом является организация таблиц в
форме массивов элементов с использованием линейного и двоичного поиска.
Простейший способ формирования таблицы выполняет добавление элементов
к свободному краю таблицы в последовательности их поступления, без дорогих
попыток их упорядочения. Поиск в этом случае нуждается в аргументе
сравнения с ключом каждого элемента таблицы, пока не будет найден нужный. Для
таблицы, включающей N элементов, в среднем будет выполнено N12 сравнений.
Если N > 20, такой способ становится неэффективным.
8.2.1. Работа с упорядоченными таблицами
Поиск может быть выполнен более эффективно, если элементы таблицы
упорядочены (отсортированы) в соответствии с некоторым естественным
порядком аргументов. Порядок определяется функцией сравнения, результаты
которого могут зависеть от количества байтов в сравниваемых аргументах и
определенного отношения порядка между кодами байтов. Проще всего упоря-
I 68 Глава 8 Работа с внутренними структурами данных информационных систем
доченность определяется по возрастанию или убыванию некоторых численных
характеристик, определяемых функцией сравнения.
В нашем случае, когда аргументами будут строки букв, наиболее
естественным будет упорядочение по внутреннему представлению строк букв. Для
внутренней формы латинских букв в коде ASCII оно совпадает с
лексикографическим порядком. Так, строки букв 'А', АВ\ 'ABC, 'AC, 'BB', расположены в
порядке возрастания кодов ASCII. Наиболее логически простым и
эффективным методом поиска в упорядоченном списке из N элементов является так
называемый двоичный поиск. Аргумент S, который необходимо найти,
сравнивается с аргументом элемента т в середине таблицы. Если этот элемент не
является нужным, после очередного сравнения мы должны просмотреть только
половину элементов, которые остались в одной из частей таблицы в зависимости от
результатов сравнения. Для простой реализации такого метода нужно
зафиксировать по номерам упорядоченных элементов верхний и нижний пределы,
которые совпадают с номерами элементов, принимавшими уже участие в
сравнениях. Т.е. для таблиц, элементы которых пронумерованы от 1 до N, избирают в
качестве начальных значений границ пс\- О и пи- N+\.
Индекс среднего элемента, с которым будет производиться сравнение,
определяется по формуле пт - (ш + п„)/2, где использовано целочисленное
деление и равенство пт — rid будет признаком невозможности дальнейшего
разбиения, а следовательно, и отсутствия нужного элемента в таблице. После
сравнения в зависимости от результата и направления упорядочения корректируется
один из пределов. Процесс повторяется для блоков меньшего размера, пока не
будет найден нужный элемент, либо не будет установлено его отсутствие.
Количество сравнений аргумента в таком алгоритме не превышает ]log2 (iV+l)[, где
]bullet[ - означает ближайшее большее целое.
Составляя эффективную программу на языке Ассемблера нужно
рационально распределять и использовать регистры специального назначения, при
этом отправной точкой должна быть базовая операция или фрагмент
программы. В случае задач поиска это операция сравнения аргументов, которую лучше
всего выполнять с помощью команды: REPZ CMPSB.
С этой командой жестко связан ряд регистров:
DS:SI - для представления строки-источника, в которых удобно хранить
адрес строки проверяемого поля таблицы;
ES:DI - для представления строки приемника, в которых удобно
хранить адрес контролируемого элемента;
СХ - в который в начале загружается длина сравниваемых аргументов в
байтах;
DF - флаг направления строковой обработки в регистр F, который
удобно держать в нуле для обработки в направлении роста адресов.
Для определения начальных значений границ индексов можно
воспользоваться данными в стеке представленных формальной структурой для Small-
модели реального режима процессоров ix86:
BINSRCHARG STRUC
dd ? ; Промежуток для ВР и адреса возврата
Язык Ассемблера в программировании информационных и управляющих систем 1 69
PROTPTR dd ?
ARGLN dw ?
TABPTR dd ?
TABLN dw ?
ELMLN dw ?
BINSRCHARG ENDS
PROC
BP
BP,SP
DS
Указатель проверяемого элемента
Длина проверяемого аргумента
Указатель начала таблицы
Количество элементов таблицы
Длина элемента таблицы
Вычисление
номера среднего аргумента
Контроль предела разбиений
Переход, если таблица просмотрена
BINSRCH
PUSH BP ; Сохранение старого ВР
MOV BP,SP ; Определение базы зоны параметров
PUSH DS ; Сохранения сегмента данных
LES DI,ProtPtr[BP] ; Подготовка адреса аргумента
LDS SI,TabPtr[BP] ; Подготовка адреса таблицы
XOR ВХ,ВХ ; Установка начальной нижней границы
lb: MOV АХ,ВХ
ADD AX,TabLn[BP]
SHR AX,1
CMP AX,BX
JZ NotFound
PUSH AX
MUL ElmLn[BP] ; Определение смещения элемента
PUSH SI ; Сохранение адреса следующего элемента
PUSH DI ; Сохранение адреса аргумента поиска
ADD SI,AX ; Вычисление адреса среднего элемента
MOV CX,ArgLn[BP] ; Подготовка длины аргумента
REPZ CMPSB ; Использование группового ресурса сравнения
POP DI ; Восстановление адреса аргумента поиска
JZ Found
POP SI ; Восстановление адреса следующего элемента
JA LCorrUp
POP BX ; Коррекция нижней границы индекса
lb
Коррекция верхней границы индекса
TabLn[BP]
Определение смещения элемента
Вычисление смещения в сегменте
Формирование признака отсутствия
JMP
LCorrUp:POP
JMP lb
NotFound:MUL ElmLn[BP]
ADD AX,SI
XOR DI,DI
JMP SHORT Lret
Found:ADD SP,4 ; Компенсация записей в стек
Lret: MOV DX,DS ; Формирование сегментной части указателя
POP DS ; Восстановление сегмента данных
POP ВР
RET
BinSrch ENDP
Приведенный пример процедуры возвращает указатель типа FAR на
найденный элемент массива или на предыдущий перед местом искомого в
случае отсутствия элемента в массиве. Правда информация о факте наличия или
I 70 Глава 8 Работа с внутренними структурами данных информационных систем
отсутствия элемента в таблице передается способом не каноничным для
соглашений о связях в языках высокого уровня: признак наличия элемента ZF = 1.
Для двоичного поиска элементы необходимо упорядочить, причем для
приведенной программы это должно быть сделано по возрастанию аргументов.
Этого можно достичь с помощью метода упорядочивающих вставок,
позволяющего поиск на каждом из этапов заполнения таблицы, и выполняется путем
смещения одной из частей таблицы. Элемент для имени S вставляется
следующим образом:
1. Используется процедура BinSrch, чтобы найти к, такое, что Sk < S<
Sk+j.
2. Элемент элемент к остается на месте; элемент к+1 сдвигается в
позицию /с+2 и т.д.
3. S размещается в элементе /с+1, который освободился на этапе 2.
Сдвиг упорядоченных элементов вперед требует поэлементного
выполнения сдвигов и приводит к достаточно сложной циклической программе,
избежать которой существенно сократив затраты времени можно, изменив
направление обработки строк командой STD и соответствующим образом загрузив
указатели строк приемника и источника.
Подготовка адреса таблицы
Вычисление длины таблицы
в байтах
Вычисление адреса конца таблицы
Вычисление длины
перемещаемой части таблицы
Установка конечного байта источника
Вычисление адреса конечного
байта приемника
Выполнение такого фрагмента можно еще более ускорить, если
организовать пересылку слов или двойных слов в команде REP МО VS.
Если таблица заполняется перед выполнением поисков, тогда
упорядочение можно выполнить после заполнения. Такая операция, как упорядочение
или сортировка, требуют существенных дополнительных затрат и не будет
оптимальной с точки зрения всего комплекса выполняемых действий.
Интересен вопрос построения таблиц символических обозначений,
имеющих блочную структуру. Языки типа Pascal или Algol и некоторые языки
спецификаций допускают использование структур вложенных блоков и
процедур. Одно и то же обозначение может быть описано и использовано много раз
в разных блоках и процедурах, и каждое такое описание должно иметь единый,
связанный с ним элемент в таблице символических обозначений. При
использовании имени возникает проблема, как найти соответствующий ему элемент в
таблице. Пример программы на языке типа Pascal с блочной структурой
приведен ниже:
MOV
LES
MOV
MUL
ADD
SUB
NEG
DEC
MOV
ADD
REP
CX,AX
SI,TabPtr[BP}
AX,TabLn[BP]
ElmLn[BP]
SX,AX
CX,SI
CX
SI
DI,SI
DI,ElmLn[BP]
MOVSB
Язык Ассемблера в программировании информационных и управляющих систем I 7 I
Begin var a, h, с, d : Real;...
Begin var e, f, a : Real;
End;
Begin var g, h : Real;...
L2: Begin var a : Real;...
End;
L3:
End
End;
Пронумеруем блоки входной программы в том порядке, в котором они
обрабатываются, т.е. в порядке просмотра начал блоков. Это естественный
просмотра блоков при последовательном грамматическом разборе слева
направо.
Правило нахождения соответствующего символического обозначения
проверяется так, что сначала просматривают текущий блок, в котором
используется символическое обозначение, потом незакрытый внешний блок
предыдущего уровня и т.д. до тех пор, пока не будет найдено описание данного
символического обозначения. Мы можем осуществить такой поиск, сохраняя все
элементы таблицы для каждого блока в смежных областях памяти с
использованием списка блоков. Мы можем пока полагать, что элементы каждого блока не
упорядочены. Элемент списка блоков вмещает номер блока-предшественника и
количество элементов в таблице обозначений для блока и указателя на эти
элементы. То есть каждый элемент списка блоков должен иметь три поля:
BlkLst STRUC
prevno dw ?
elnumb dw ?
elpoint dd ?
BlkLst ENDS
Такая таблица символических обозначений с "блочной структурой"
может использоваться каждый раз, когда мы имеем ситуацию вложенных блоков,
например, при генерации и обработке макрорасширений, где к символическому
обозначению, описанному в блоке, можно обратиться только в этом блоке.
Например, если создать блочные таблицы для таких операторов с блочными
структурами, как for, if, while, case, то это может облегчить диагностику
перехода в блок извне. С другой стороны задачу вложенных блоков можно решить
так, как это делается в макроассемблере, заменяя описанные как локальные,
вновь сгенерированными именами или сохраняя рядом с символическим
обозначением номер блока, в котором определено это обозначение. Тогда поиск
характеристик имени осуществляется циклическим обращением к программе
поиска по комбинированному аргументу, включающему два поля: образ имени
и номер блока его определения. Поиск заканчивается при обнаружении первого
идентичного имени.
1 72 Глава 8 Работа с внутренними структурами данных информационных систем
С другой стороны использование блочных структур таблиц
целесообразно и при больших объемах таблиц, когда для манипуляций с большими
блоками данных требуется их разграничение по сегментам. В соединении с
разными методами упорядочения они дают разные интересные реализации таблиц и
методов поиска в них.
Сортировка в алфавитно-цифровом порядке имеет смысл для больших,
но фиксированных или, по крайней мере, относительно стабильных по
размерам таблиц, например, словарей (орфографических, переводных, толковых и
т.д.), сортировка которых выполняется либо на этапе проектирования, где нет
жестких требований по скорости ее выполнения, либо на специальных этапах
обслуживания словарей. Поэтому для сортировки можно пользоваться
практически любым эффективным методом сортировки, описанным в литературе. При
этом особого внимания заслуживают методы, основанные на рекурсивной
декомпозиции таблиц.
Эффект от программирования сортировке на основе сравнений
аргументов поиска невелик, поэтому здесь можно упомянуть лишь реликтовый прием
перестановки элементов, применявшийся в процессорах без команды обмена.
Три машинные команды:
XOR
XOR
XOR
АХ,
ВХ,
АХ,
ВХ
АХ
ВХ
по результату эквивалентны команде XCHG АХ,ВХ.
8.2.2. Поиск по прямому адресу и хеш-поиск
В современных вычислительных системах глубокое развитие получили
наиболее эффективные методы поиска по разветвленным ссылочным
структурам и поиск по аргументу или его функциям, используемым в качестве прямого
адреса. В главах 3 и 4 мы уже рассматривали применение XLAT, реализующей
по своей сути поиск по прямому адресу, для классификации букв
символических обозначений. Эта же операция может использоваться для анализа более
коротких полей при использовании агрегатов типа RECORD, однако если
аргумент не помещается в рамки одного байта можно применить команду
пересылки с индексированием аргумента:
MOV SI,AX
MOV AL/[ВХ][SI] ; Извлечение классифицирующего байта
Если эффективность этого приема на программном уровне может быть
сомнительной, хотя и обеспечивает высокую гибкость при табличных
настройках, его аппаратная реализация может быть весьма эффективной при
использовании малого числа микросхем памяти.
Хеш-поиск строится на основе целенаправленного информационного
поиска по прямому адресу, который является наиболее быстрым за счет ис-
Язык Ассемблера в программировании информационных и управляющих систем I /3
пользования аргумента А\ как прямого адреса, либо индекса элемента таблицы.
Поиск по прямому адресу выполняется в сегменте таблицы с начальным
адресом Ан, в которой каждый элемент находящийся по адресу А = Ан + А\. Такой
поиск в чистом виде используется лишь при малых диапазонах аргументов,
например, при решении задач перекодировки и классификации байтов во время
лексического анализа. Но при больших диапазонах значений аргументов, на
порядок превышающих размеры сегментов реального режима, строится так
называемая хеш-функция h(Ai)} которая должна давать разные значения для
разных элементов таблицы с наиболее плотным расположением элементов в памяти.
По методу хеш-поиска аргумент преобразуется в индекс элемента
таблицы, построенной как массив. Индекс формируется вычислением хеш-функции
(хеширования) аргумента с выполнением по возможности арифметических и
логических операций над буквами аргумента поиска. Простейшей
хеш-функцией может быть внутреннее воспроизведение первой буквы аргумента. Пока для
двух разных аргументов результаты хеширования разные, время поиска
определяется временем хеширования. При больших объемах таблиц получается
большая экономия по сравнению со временем поиска в неупорядоченной
таблице и даже со временем двоичного поиска. Однако возникают трудности, если
для разных аргументов формируются одинаковые значения хеш-функций.
Такая ситуация называется коллизией. В одном элементе такой таблицы может
быть расположен только один из этих аргументов, так что необходимо найти
место для других, имеющих то же значение хеш-функции. Кроме того, следует
различать, какой из возможных аргументов фактически находится в таблице,
для чего в таблице сохраняются образы аргументов либо указатели на них, а
при инициализации таблицы должно использоваться какое-то значение,
запрещенное среди кодов букв либо указателей (чаще всего, 0).
Качественная хеш-функция распределяет вычисленные адреса
равномерно по всему имеющемуся пространству адресов, так чтобы коллизии не
возникали слишком часто. Хеш-функция, рассмотренная выше, очевидно, плохая,
потому что все имена, которые начинаются с одной буквы, будут иметь один и
тот же адрес. Перед анализом возможных хеш-функций отметим основные
способы разрешения коллизий: рехеширование и метод связанных ссылок. По
первому методу приходится простым алгоритмом (в простейшем варианте
линейного поиска) отметить свободное место в таблице, а за вторым - произвести
ссылку на свободное место возможно и в новом сегменте таблицы, что с одной
стороны снимает ограничение на размер таблицы, а с другой стороны
предоставит возможность построения древовидных структур.
Для таблицы с N элементами функция хеширования h(A) будет иметь
значения в пределах 0 .. 7V—1. Для разрешения коллизий методом рехеширова-
ния литература предлагает такие способы:
• линейное рехеширование линейным поиском свободного места
неподалеку от вычисленного хеш-адреса, наиболее известным, и, скорее всего,
наименее эффективным из-за нагромождения коллизионных цепочек;
• функциональное рехеширование, выполняемое через дополнительную
хеш-функцию либо функцию произвольного типа.
I 74 Глава 8 Работа с внутренними структурами данных информационных систем
Причина низкой эффективности этого метода становится достаточно
ясной из примеров возникновения после нескольких коллизий, разрешенных
таким образом, для групп элементов, которые скапливаются вместе, образуя
длинные цепи дополняемых элементов. Оценка среднего числа сравнений Е для
поиска одного элемента и имеет вид: Е = (1 - /у/2) (1 - //), где //- коэффициент
заполнения. Таким образом, если таблица заполнена на 10%, мы можем
ожидать в среднем 1,06 сравнений; если она восполненная наполовину, - 1,5
сравнений, и если таблица заполнена на 90%, - 5,5 сравнений. Отметим, что Е зависит
не от размера таблицы, а лишь от степени ее заполнения.
Мы можем найти лучшие значения/?!, pi, ..., но даже с рассмотренными
значениями этот метод все же быстрее поиска двоичным методом.
Предположим, что таблица с 1024 элементами засполнена наполовину, то есть
восполнены 512 элементов. В процессе двоичного поиска мы ожидаем от 9 к 10
сравнений, в то время как в случае хеш-поиска только 1,5. Время поиска для
упорядоченных и неупорядоченных таблиц зависит не от максимального размера
таблицы, а от текущего числа элементов.
Если таблица имен заполнялась с использованием метода цепочек для
разрешения коллизий, то к ней всегда можно добавить новый блок элементов.
При этом информационная система допускает динамическое разграничение
памяти, потому что функция хеширования дает индекс в хеш-таблице, а ссылки
на сегменты таблицы имен, выполняются только с помощью указателей. Таким
образом, максимально количество элементов в таблице имен, в отличие метода
рехеширования, не ограничено (но ограничено максимальное количество
указателей). Отметим также, что в начальном определении нуждаются только
указатели, а не сами элементы таблицы имен. Количество указателей, находящееся
в пределах, от 100 до 300, в то время как элементов в таблице намного больше.
Как только все имена внесены в таблицу, хеш-таблицу можно уничтожить, а ее
место использовать для других нужд, если все имена будут заменены на номера
элементов либо нечто подобное, например указатели. При этом для диагностики
ошибок желательно сохранить образы элементов в легко доступной форме. Для
метода цепочек приходится по одному добавленному указателю для каждого
элемента, и в большинстве случаев это оправдано.
В современных системах просматривается тенденция раздела
интегрированной таблицы на три части, которые сохраняются отдельно:
• собственно неупорядоченная таблица с элементами обычно
фиксированной длины, в которой сохраняются ссылки на сегмент символьных
образов;
• сегмент образов, в котором для лингвистических систем сохраняются
буквенные образы, кодированные в ASCII, а для разнообразных
интеллектуальных систем - кодированные в легковостанавливаемой форме
графические, звуковые образы и образы других типов, которые
применяются в методах multiMedia;
• упорядочивающая надстройка с использованием хеш-адресации, либо
других методов упорядочения, либо индексации.
Язык Ассемблера в программировании информационных и управляющих систем I 75
При использовании такого комплекса структур внутренняя форма
воспроизведения символических обозначений в трансляторах в общем случае
может иметь многоуровневый облик. В качестве примера демонстрации
эффективности техники программирования хеш-поиска на Ассемблере рассмотрим
таблицу, за основу которой возьмем пример из главы 4, но вынесем из таблицы
образы аргументов в отдельный сегмент, и организуем хеш-таблицу как
надстроечный массив указателей.
TbElem STRUC
KeyPtr dd
TpSize
Segm
Offst
dw ?
dw ?
dd ?
Nunber dd ?
TbElem ENDS
ImageBlk SEGMENT
Структура элемента таблицы имен
пользователя
Указатель на буквенный аргумент
в блоке образов
Закодированный тип/размер
Номер сегмента в модуле
Смещение до 4 байтов для
32-битовых процессоров
Количество элементов
Конец структурного блока
(описания сегмента)
Сегмент образов аргументов
db 65536 dup (?)
ImageBlk ENDS
TbSeg SEGMENT
ImageSeg dw seg ImageBlk
Tb TbElem 4093 dup <0, ?,?,?,?>
Статическое резервирование
памяти образов
Конец сегмента образов
Начало сегмента таблицы
ele EQU $
TbSeg ENDS
Hashlndx SEGMENT
dw 4093 dup (0)
Ehash
ElNum
TbSegPtr
LnElm
Hashlndx
HashSrchArg
ProtPtr
ArgLn
HashPtr
LimColl
dw
dw
dw
dw
END
OFFSET $
4093
seg TbSeg
16
S
STRUC
dd
dd
dw
dd
dd
?
9
9
9
? ; Uv
HashSrchArg ENDS
HashSrch PROC
PUSH BP
Резервирование
; элементов
Граница размещения элементов
Конец сегмента таблицы
Начало сегмента хеш-индекса
Подготовка пустого сегмента
хеш-индекса
Количество статических элементов
в сегменте
Длина элемента
Конец сегмента хеш-индекса
Промежуток для ВР и адреса
возврата типа near
Указатель проверяемого элемента
Длина проверяемого аргумента
Указатель начала хеш-индекса
Предел линейного устранения коллизий
Сохранение старого ВР
I I \) Глава 8 Работа с внутренними структурами данных информационных систем
MOV BP,SP ; Определение базы зоны параметров
PUSH DS ; Сохранение сегмента данных
LES DI,ProtPtr[BP] ; Подготовка адреса аргумента
LDS SI,HashPtr[BP] ; Подготовка адреса таблицы
MOV СХ,[EINum] ; Подготовка количества элементов
CALL HashFunc ; Получение значения хеш-индекса в ВХ
MOV CX,LimColl[BP] ; Подготовка количества элементов
LbCollrCMP
JZ
PUSH
MOV
PUSH
LDS
PUSH
PUSH
PUSH
MOV
WORD PTR[BX],0
Lins
DS
DS,[TbSegPtr]
DS
Контроль свободного места
На вставку элемента
Сохранение сегмента данных
Подготовка адреса таблицы
Сохранение сегмента данных
SI,DS:Tb.KeyPtr[ВХ] ; Загрузка указателя
; на образ
СХ ; Сохранение счетчика коллизий
SI ; Сохранение адреса следующего элемента
DI ; Сохранение адреса аргумента поиска
CX,ArgLn[BP] ; Подготовка длины аргумента
REPZ CMPSB
POP
POP
POP
JZ
POP
POP
LEA
CMP
JB
XOR
DI
SI
; Использование группового ресурса
сравнения
Восстановление адреса аргумента поиска
Восстановление адреса следующего
элемента
; Восстановление счетчика коллизий
СХ
Found
DS ; Восстановление сегмента данных
DS ; Восстановление сегмента данных
BX,2[BXj ; Переадресация
ВХ,DS:[EHash] ; Контроль границы хеш-индекса
LeColl
ВХ,ВХ
eColl:LOOP LbColl
XOR AX,AX
XOR
Lret:POP
POP
RET
Lins:CALL
JMP
Found:ADD
Lret:MOV
JMP
HashSrch ENDP
DX,DX
DS
BP
Ellns
Lret
SP,4
Циклический переход, если таблица
не исчерпана
Установка признака переполнения
сегмента
Восстановление сегмента данных
Обращение к процедуре вставки
Компенсация записей в стек
DX,DS:[SegTbSeg]
Lret
Формирование сегментной
части указателя
Приведенная программа по существу выполняет хеш-поиск по сегменту
надстройки Hashlndx, который можно использовать в качестве индекса. При
Язык Ассемблера в программировании информационных и управляющих систем 1 77
построении процедуры вставки удобно использовать вполне реальное
допущение, что образ контролируемого элемента дописан в конец текущего сегмента
образов ImageBlk, а сам элемент в конец массива элементов ТЬ. Для
32-битовых процессоров программу можно упростить, используя вместо регистра DS,
сохраняемого в стеке дополнительные регистры FS и GS.
Выбор наиболее эффективных хеш-функций определяется, прежде всего,
целью применения таблиц - для построения оперативных таблиц необходимо
обеспечить наиболее вероятное равномерное в среднем рассеивание значений.
Для построения фиксированных и более постоянных таблиц либо словарей
целесообразно использовать хеш-функции для равномерного рассеивания
аргументов по блокам разных уровней. Их целесообразно определять гак, чтобы
они обеспечивали относительно равномерное распределение ключей по блокам
на верхних ступенях таблицы и равномерное разграничение без чрезмерного
избытка на нижних уровнях. При использовании хеш-адресации в системах
кодирования и защиты информации от несанкционированного доступа наряду с
аргументами либо ключами поиска при формировании хеш-функции могут
встраиваться ключи кодирования, без которых не может быть восстановлена и
воспроизведена сохраненная текстовая информация.
Хеш-функция определяет метод хеширования. Чаще всего используются
такие методы:
• метод остатка, по которому h{Ai) = A\ mod N, где N - достаточно
большое простое число, которое соответствует размеру сегмента таблицы,
например 1009;
• мультипликативный метод, при котором h(Ai) = С/ А\ mod N, где С/-
целочисленные константы из интервала [О, N-I];
• метод выделения битов, по которому h(Ai) формируется путем сцепления
нужного количества битов, извлеченных из определенных позиций
указанной строки;
• метод фрагментации аргумента, по которому битовая строка,
соответствующая аргументу А, делится на фрагменты, равные по длине
хеш-адресу. Объединение фрагментов можно выполнить такими разными
операциями, как сумма по модулю 2 и др.
При использовании произведения всех сегментов строится новый метод
перехода к новому основанию, по которому аргумент Ai превращается в код по
правилам системы счисления с базовым основанием. Полученное число
ограничивается значением размера сегмента таблицы.
Если имя S, являющееся аргументом хеширования, занимает больше
места, чем отводится для воспроизведения индекса, значит на первом этапе
хеширования из S формируется одно машинное слово Sb. Например, в
32-битовых компьютерах целесообразно в одном элементе размещать до 4-х букв, а в
16-битовых - до двух букв. В простейшем случае, Sb определяется сложением
всех элементов с помощью обыкновенного сложения либо с помощью
поразрядного сложения по модулю 2 ("исключающее ИЛИ") в комбинации с
поразрядными сдвигами, обеспечивающими относительно равномерное сохранение
количества двоичных нулей и единиц в результате и уменьшают вероятность
1 78 Глава 8 Работа с внутренними структурами данных информационных систем
возникновения скоплений элементов в таблице. Из теории генерации
псевдослучайных чисел известны детерминированные методы, основанные на
вычислении линейных комбинаций элементов аргумента поиска. Эти комбинации
генерируют последовательности с законом распределения, близким к
равномерному, путем получения остатков от деления на размер таблицы (в диапазоне их
определения) практически при любых значениях коэффициентов.
На втором этапе из Sb вычисляется конечный индекс, причем, это можно
выполнить несколькими способами.
1. Умножить Sb на себя и использовать п средних битов как значения
функции хеширования, если таблица имеет 2" элементов. Поскольку п средних
битов зависят от каждого бита Sb, этот метод дает хорошие результаты.
2. Использовать какую-то логическую операцию, например,
"исключающее ИЛИ", над несколькими частями Sb.
3. Если в таблице есть 2п элементов, розбить Sb на п частей и
просуммировать их. Использовать п крайних правых битов результата как хеш-индекс.
4. Разделить Sb на длину таблицы и остаток использовать как хеш-индекс.
Все эти методы применялись и давали удовлетворительные результаты.
Можно было бы разработать и другие методы, лишь бы быть уверенными, что
на множестве аргументов, к которому будет применена функция хеширования,
она даст достаточно произвольные адреса. В нашем случае, когда начальное
формирования таблицы происходящее путем занесения в нее всех
резервированных слов и стандартных идентификаторов языка, стоит проверить
начальное состояние таблицы и посмотреть на количество возникших коллизий. Хеш-
функция может быть достаточно хорошей с точки зрения статистики, но может
так случаться, что несколько резервированных слов указывают на один и тот
же адрес.
Нужно также отметить, что два крайних левых бита в коде ASCII и его
разновидностях, применяемых в современных компьютерах, для всех
латинских букв совпадают два крайних левых бита (01), а для букв кириллицы -
старший бит (1). Так, при создании хеш-функций необходимо быть
внимательным, чтобы не получить функцию со слишком узким диапазоном значений.
В терминах языка Ассемблера в версиях, которые изготовливаются
фирмой Borland подобный алгоритм может быть реализован на таких данных как
стандартные строки языка С с группированием символов в четырехбайтовые
конструкции со сдвигами:
Сохранение указателя аргумента
Очистка накопителя суммы
Накопление сумм со сдвигом
Добавление букв умноженных на 256
Продвижение по буквам
CMP BYTE PTR ES:[DI],Q ; Контроль окончания
HashFunc
PUSH
XOR
MOV
Lb;ADD
ADC
ADD
ADC
ADD
PROC
DI
AX, AX
DX,AX
AX,ES:[DI]
DL,ES:[DI+2]
AX,ES:[DI+1]
DL,ES: [DI+3]
DI,4
Язык Ассемблера в программировании информационных и управляющих систем 1 79
; строки ASCIIZ
Lb
'DIV CX ; Взятие модуля
MOV BX,DX ; Кодирование в целевой регистр
REPT 4
SHL ВХ, 1 ; Умножение на 16 для получения смещения
ENDM
POP DI ; Восстановление указателя аргумента
RET
HashFunc ENDP
Хеш-функции подобного типа использовались в реализации
компиляторов с языков высокого уровня для IBM/360/370. Каждое имя и константу
входной программы при ее обработке в системе программирования удобно
заменить ссылкой на соответствующий элемент в таблице символических
обозначений. После выполнения такого преобразования хеш-таблица может быть
отброшена, а при сохранении во внутренней форме указателя или его номера, как
в приведенном примере, не произойдет никаких информационных потерь, т.е.
исходный текст может быть восстановлен с точностью до пробелов и
переводов строк. Надстройка в такой форме и таблицы после преобразования
исходных текстов во внутреннюю форму не нужны.
В надстроечной структуре в процессе кодирования рядом с базовым
элементом - указателем на поисковый образ необходимо иметь указатель на
информационную часть элемента в таблице. Такие надстройки чаще всего
называют индексами по аналогии с библиографическим индексированием
литературных источников.
8.2.3. Ссылочные и древовидные структуры
Другая линия развития базовых структур таблиц основывается на
ссылочных структурах. Использование ссылок и указателей устраняет
необходимость множественных пересылок при сортировках таблиц. Наличие одно- и
двунаправленных ссылок вдоль массива элементов приводит к построению
одно- и двухсвязных списков, в которых практически нет возможности получить
эффект от упорядочения при поиске в таблице.
Однако, если усложнить структуру ссылок, т.е. организовать их в
соответствии с некоторым определенным порядком. Например, для организации
двоичного поиска необходима древовидная ссылочная структура,
осуществляющая навигацию процесса поиска. С другой стороны таблицы
символических обозначений, имеющие древовидную структуру можно рассматривать как
обобщение хеш-таблиц, а также как обобщение таблиц, упорядоченных в
алфавитной либо лексико-графической последовательности. Простейшая
реализация этого метода базируется на упорядочении элементов с помощью бинарного
или двоичного дерева, в котором к каждому узлу может быть "подвешено" не
более двух поддеревьев. Каждый узел дерева представляет собой
сформированный элемент таблицы, причем корневой узел будет первым проверяемым
элементом. Первые в соответствии с упорядочением указатели посылаются на эле-
1 80 Глава 8 Работа с внутренними структурами данных информационных систем
менты с меньшим аргументом, а вторые - на элементы с большим аргументом.
Предположим теперь, что необходимо записать в таблицу имя 'D'. Для него
выбирается левое поддерево, так как 'D' < 'G', находящегося в корне (рис. 8.1).
Теперь запишем имя 'М\ Потому что 'G' < 4М\ для 'М' выбирается правое
поддерево 'G'. И наконец, запишем имя 'Е'. Потому что 'Е' < 'G', пройдем левой
дугой для формирования 'D\ 'D' < 'Е', тогда мы выбираем дугу, которая ведет
связь от 'D'.
I 'G'j ptr<D|ptr<M
'D-j ptr<A | ptr<E I ['Ml null [ null
I 'A'j mdl | irtr<B]|iErj null | ptr<F
I'Br[ null | null | | '1' | null I null
Рис. 8.1. Пример бинарного дерева
Принцип построения бинарного дерева такой, что для деревьев с
одинаковым содержимым можно построить много разных структур, которые
отличаются корнями и промежуточными вершинами. Наиболее важной
характеристикой бинарного дерева является его сбалансированность. Сбалансированным
деревом считается, такое, в котором количество уровней связей в иерархии
элементов не отличается по любой паре путей более чем на 1.
Формальную спецификацию метода поиска в бинарных деревьях можно
определить через отношение элементов соседних уровней:
А [/+1.2/-1 ] < А[/+1, 2/],
где / - номер уровня, j- номер элемента на соответствующем уровне.
Для реализации метода можно в каждом элементе отметить два поля
указателей, одно поле для левой, а другое - для правой ветви. Количество
нужных сравнений существенно зависит от последовательности, в которой
поступают символические обозначения. Например, если они поступают в такой
последовательности: 'А', 'В', 'С, 'D', 'Е\ Т', то, чтобы найти Т\ следует сделать
6 сравнений. Можно перестроить дерево на определенных этапах обработки,
чтобы сбалансировать его и уменьшить максимальный путь поиска.
Балансировка дерева является довольно сложной работой, которую необходимо
выполнять периодически после нарушений баланса при заполнении таблицы.
Приведенным бинарным деревом можно воспользоваться для печати списка
символических обозначений в алфавитном порядке латинского алфавита. При работе с
русским, а особенно с украинским алфавитом возникают проблемы, связанные
с несоответствием кодов нескольких букв их традиционному месту в алфавите.
При реализации на языке Ассемблера вместо слов указателей на элемент
в сегменте Hashlndx в примере хеш-поиска следует использовать более слож-
Язык Ассемблера в программировании информационных и управляющих систем 181
ные структуры, включающие указатели на элементы таблицы, а также на
соседние узлы дерева, что довольно существенно повлияет на программу двоичного
поиска. Пример процедуры реализован из предположения, что указатели на
пустые (тупиковые) ветви дерева формируются в виде нулевых слов.
BinTrElm STRUC
ElmPtr
DnPtr
UpPtr
BinTrElm
dw ?
dw ?
dw ?
ENDS
Структура элемента надстройки
бинарного дерева
Указатель на элемент
Указатель на нижнего соседа
Указатель на верхнего соседа
Конец структурного блока
(описания сегмента)
BnTrlndx SEGMENT ; Начало сегмента бинарного индекса
BinTrElm 4093 dup <Q,0,0> ; Подготовка пустого дерева
Ehash dw OFFSET $
EINum dw 4093 ; Количество статических
; элементов в сегменте
TbSegPtr dw seg TbSeg
LnElm dw 16 ; Длина элемента
BnTrlndx ENDS ; Конец сегмента бинарного индекса
BnTrSrch PROC
PUSH BP ; Сохранение старого ВР
MOV BP,SP ; Определение базы зоны параметров
i PUSH DS ; Сохранение сегмента данных
LES DI,ProtPtr[BP] ; Подготовка адреса аргумента
LGS BX,TabPtr[BP] ; Подготовка адреса надстройки
MOV FS,GS:[TbSegPtr]; Подготовка адреса сегмента
; таблицы
lb: TEST BX,BX
J2 NotFound ; Переход, если таблица просмотрена
MOV SI,GS:ElmPtr[BX] ; Загрузка указателя на элемент
PUSH SI
LDS SI,FS:Tb.KeyPtr[SI] ; Подготовка адреса образа
PUSH DI ; Сохранение адреса аргумента поиска
MOV CX,ArgLn[BP] ; Подготовка длины аргумента
REPZ CMPSB ; Использование группового ресурса
;сравнения
POP DI ; Восстановление адреса аргумента поиска
POP SI
JZ Found
JA LCorrUp
MOV BX,DnPtr[BX] ; Коррекция нижней границы индекса
JMP lb
CorrUp:MOV BX,UpPtr[BX]; Коррекция верхней границы индекса
JMP lb
NotFound:MOV AX,BX ; Формирование смещения в сегменте
MOV DX,BX
XOR DI,DI ; Формирование признака отсутствия
JMP SHORT Lret
1 о2 Глава 8 Работа с внутренними структурами данных информационных систем
Found:MOV AX,SI ; Формирование смещения указателя
LretrMOV DX,FS ; Формирование сегментной части указателя
POP4 DS ; Восстановление сегмента данных
POP ВР
RET
BnTrSrch ENDP
В этом примере использованы возможности сегментов 32-битового
режима, хотя базовая и индексная адресации, сохранены характерными для 16-
битового режима. Интересно отметить, что в последнем примере оказались
задействованными все возможные сегментные регистры 32-битового процессора,
что еще раз подтверждает отслеживание фирмой Intel тенденций в современном
системном программировании и программировании информационных систем.
При применении поиска по бинарному дереву нужно учитывать, что
самой сложной и важной задачей в этом случае является поддержка баланса
ветвей дерева, так как его несоблюдение резко ухудшает характеристики метода.
Дальнейшее развитие методов работы с древовидными структурами
привел к использованию более ветвленных древовидных структур, получивших
название В-деревьев. В них сохраняется отношение упорядочения связей узлов
и добавляется отношение упорядочения внутренних элементов одного узла,
которые должны быть согласованы. Для лексикографических сравнений это
одинаковые отношения аргументов предшествующих и последующих элементов.
Узлы В-дерева сохраняют информацию о нескольких упорядоченных
элементах. Обычно количество элементов в узле не очень велико и лежит в пределах 3
< п < 256.
В качестве закона упорядочения данных в узлах В-деревьев и между их
узлами могут использоваться правила упорядочения по алфавиту и правила
упорядочения по хеш-функции. На рис. 8.2 приведен пример В-дерева, которое
вмещает, ту же информацию, что и бинарное дерево с рис. 8.1, но занимает
лишь два уровня иерархии. При использовании хеш-функций для упорядочения
в узле дерева целесообразны алгоритмы заполнения узлов сверху вниз, а при
алфавитном упорядочении узлов - построение узлов новых уровней снизу
вверх.
'D7! 'D-1 ptK'D'l 'G' I ptK'G'J'M* |ptr<M'|
null
null
I 1/OlptK'A'l 'A' | ptK'A'l 'B'|ptr<'B'| ... | null ] | 1/l| ptK'E'l 'E11 ptK'li'j F |ptr<F'| ... |
null null null null null null
Рис. 8.2. Пример В-дерева
При реализации на языке Ассемблера требуется другая структура узла
в виде массива структур фиксированной длины:
Язык Ассемблера в программировании информационных и управляющих систем "1 83
BTrElm STRUC ; Структура элемента надстройки
; дерева
ElmPtr dd ? ; Указатель на элемент
DnLVPtr dd ? ; Указатель на связанный с ним узел
; нижнего уровня
BTrElm ENDS ; Конец структурного блока (описания сегмента)
Элементы в узле дерева должны быть упорядочены либо в алфавитном
порядке, либо в соответствии с некоторой многоуровневой хеш-функцией. При
алфавитном упорядочивании и заполнении имеющихся узлов В-деревьев,
новые узлы лучше формировать снизу вверх, а при использовании хеш-функций -
сверху вниз.
8.3. Методы сравнения и сопоставления
аргументов и ключей поиска
Даже при решении таких относительно простых задач, как поиск в ИБ
языков программирования, мы имели возможность увидеть, что результаты и
время поиска существенно зависят от законов, функций или операторов
сравнения. С одной стороны эти законы определяются синтаксисом представления
аргументов поиска, например, либо форматом символьных строк в языке Pascal в
виде массива управляемой длины, начальный (нулевой) элемент которого
содержит длину, либо в виде строк в коде ASCIIZ с нулевым конечным байтом,
принятых в языке С.
В сложных информационных системах широкое распространение
получили задачи поиска по комплексным и векторным критериям, в которых
направления поиска задаются не одной характеристикой, а комплексами в виде:
• минимаксных отношений над отдельными характеристиками или
функционалами,
• ограничительных отношений по характеристикам и функционалам;
• векторных отношений, определяющих поиск в многомерных пространствах.
Более того, для решения многих задач методами поиска далеко не всегда
требуется соблюдение строгого равенства с контролируемой координатой
объекта или координатой поиска и поэтому вместо команды REPZ CMPSB;
использованной в предыдущем параграфе в качестве базового метода сравнения
в общем случае нужно использовать некоторую обобщенную функцию
сравнения GenCmp, в которой задается проверяемое отношение над полями элемента
ИБ и включает в качестве аргументов:
• ссылку на функционал, определяющий одно из отношений меры
близости: равенство, ограничение типа больше-меньше, ограничение типа
вхождение, ограничение для вычлененной части кодов и др.;
• список полей, входящих в функционал и способы доступа к ним.
• В плане реализации этих функций могут использоваться:
I 84 Глава 8 Работа с внутренними структурами данных информационных систем
• циклы для организации сравнений по нескольким полям, с учетом длин
и упорядоченности полей;
• операции сравнений и условных переходов для формирования
результатов отношения, причем на уровне Ассемблера эти результаты могут
определяться флагом ZF по аналогии с командой CMPSB;
• операции выделения частей полей типа RECORD через AND и MASK;
• операции пропуска допустимых отклонений в сравниваемых объектах.
Последний пункт особенно актуален в ситуациях, связанных с
построением систем автоматизированной коррекции информации, например,
программной или текстовой, для которых характерны ошибки подготовки или
ввода данных. В таких системах важным является поиск всех близких образов в
соответствии со списком контролируемых ошибок, среди которых наиболее
распространены замена, потеря и вставка букв. Поскольку вставка и замена
могут произойти в любом месте слова, то для поиска сходных объектов, по
существу нужно просмотреть все таблицы и словари. Для рациональной
организации диалогового взаимодействия пользователя с ИС важно определить по-
( гедовательность поиска, начиная с форматов, близких к контролируемому ар-
\тенту и кончая отдаленными. Если формально определена мера близости, то
^гда можно хранить указатель на ближайшее в соответствии с этой мерой
решение.
Решение этой задачи в общем виде настолько сложное, что для ее
решения необходимо строить некоторую виртуальную машину или, по крайней
мере, виртуальный метод сравнения или сопоставления.
8.4. Поиск в линейных однородных данных
В ИБ современных систем искусственного интеллекта возникает
необходимость сохранения и обработки организационно неструктурированной
однородной информации о текущих фактических состояниях, ситуациях и картинах
системы, и базовой модели проблемной области, для которой построена
система. Такая информация может быть представлена в формах, имеющих
математическое, текстовое, графическое, звуковое или какое-то другое отображение.
Принципы эффективной организации работы системы учитывают разный
характер представления информации на устройствах воспроизведения, включая
устройства отображения, чувствительных устройствах восприятия и
устройствах прагматической обработки. Среди этих представлений легче всего
обобщаются форматы данных именно для последнего типа обработки.
Информационные структуры таких систем, строятся на основе
справочников или словарей с двумя путями достижения данных: через идентификацию
объектов в базовой форме отображения для последующей перекодировки или
преобразования во внутреннюю форму и через числовые коды,
воспроизводящие отдельные элементы ситуаций или картин в любой форме для
последующей обработки и подготовки картин или ситуаций для отображения перед
пользователем. Использование современных методов построения композици-
Язык Ассемблера в программировании информационных и управляющих систем I 85
онных объектов разрешает с одной стороны синтезировать, а с другой -
анализировать картины и ситуации для любых форм отображения при их
значительной сложности.
С другой стороны использование для создания больших ИБ сетей и
комплексов ЭВМ и использование очень мощных конфигураций компьютеров по
быстродействию оперативной памяти и дисковым накопителям приводит к
целесообразности использования распределенных ИБ с размещением частей ИБ вблизи мест их
интенсивного использования. Таким образом, обобщенная цель
усовершенствования программных систем информационного поиска может быть сформулирована
так: построение ИБ предельно простой структуры, позволяющей сохранять в
единой форме аналитическую и фактическую информацию о проблемной области, а
также о структурных связях понятий в создаваемых базах данных (БД).
Рассмотрим как базовый пример построение БД, ориентированных на
сохранение и анализ текстовой информации. В современном подходе,
ориентированном на преимущественную обработку данных в рамках реляционных БД,
таких как FoxPro, Dbase, Paradox, Oracle и др., сохранение фрагментов
текстовой информации затруднено и она накапливается в memo-блоках, имеющих
достаточную для этого длину, или в текстовых полях управляемого размера.
Реляционные БД, приспособлены для хранения в файлах, в которых каждая
запись представляет собой комплекс аргументов поиска и характеристик,
сформированных в соответствии с прототипом записи, а тексты сохраняются в
дополнительных файлах, связанных механизмом ссылок.
В начале проектирования систем анализа и сохранения текстов
определяют структуры данных и формы их внутреннего отображения:
• структура внутреннего сохранения текста;
• структура словаря, для кодированного сохранения текста;
• структуры связывания текстов по ключевым словами или
гипертекстовые структуры для быстрой концентрации информации по
определенным ключевым словам.
Требования для работы с текстами соответствуют общим условиям для
ИБ: предельная простота хранения и обработки текстов, в этих условиях
желательна предельная компактность сохранения информации. Важным фактом
подсистем текстовой обработки является то, что поиск в словаре, навигация в
гипертекстовых и распределенных информационных структурах, а также
анализ текстов являются относительно независимыми задачами, для которых
используются независимые методы.
Для построения текстовых БД обычно используют преобразования
текстовых форм данных в некоторую специальную машинную форму. Таким
образом, для построения БД, ориентированной на синтаксический и семантический
анализ текста, необходимо создать словарь для преобразований, в котором будут
находиться синтаксически корректные образцы преобразуемых слов. В системах с
более глубоким семантическим уровнем анализа к орфографическим словарям
добавляются словари с пояснением вариантов семантики для каждого слова
важного в проблемной области, для которой создается словарь. Численную и
табулированную информацию текстов при этом можно представлять в специ-
I Ou Глава 8 Работа с внутренними структурами данных информационных систем
альной форме с указанием типа. Подобные БД могут применяться в
автоматизированных системах подготовки документов. Т.е. при обработке
комбинированных текстов обобщенный формат кодирования опирается на
последовательное отображение текстов во внутренней форме с ведущим кодом типа в начале
каждого элемента. После предварительного определения ключевых слов
проблемной области рядом с текстами может быть построена индексная
связующая структура, образующая так называемый гипертекст.
Формат хранения словарей обычно соответствует традиционному
формату таблиц и обеспечивает прямое и обратное преобразование информации в
задачах кодирования, информационного поиска и машинных переводов. Для
обратных преобразований удобно использовать поиск по прямому адресу
размещения эталонного образа или его разновидности. В текстовых системах,
ориентированных на эксплуатацию в условиях флективных языков, в том числе и
украинского и русского языка, возникают значительные осложнения по
сравнению с английским языком, из-за необходимости использования специальных
методов морфологического анализа.
При построении программ преобразования во внутреннюю форму
текстов на русском и украинском языке необходимо выполнять сопоставление
форм отдельных слов с формами получаемыми из основ слов, сохраняемых в
словаре. Логически простейшая организация поиска соответствующей
словарной формы слова состоит из последовательной генерации всех форм и
сравнения с преобразуемым словом для каждой близкой основы. Существенно
ограничить затраты на сравнение можно было бы, если б не довольно частые,
особенно в украинском языке, изменения букв основ и беглые гласные. Считая, что
основа в словаре представляется структурой:
(Структура представления слова в словаре
Байт синтаксического управления
Байт морфологического управления
Байт длины изменяемой части
; Байт длины основы
1 ; Основа слова
; Конец структурного блока
Можем построить процедуру поиска требуемой формы слова по
заданной основе в словаре, которую можно рассматривать как вариант обобщенной
процедуры сравнения.
WrciRoot
SyntCtr
MrphCtr
ModLn
RootLn
Root
WrdRoot
STRUC
db ?
db ?
db ?
db ?
db f
ENDS
HashSrchArg STRDC
dd ?
ProtPtr dd ?
ArqLn dw ?
VcbPtr dd ?
HashSrchArg ENDS
VcbCmp PROC
PUSH БР
Промежуток для ВР и адреса
возврата типа near
Указатель проверяемого слова
Длина проверяемого аргумента
Указатель слова в словаре
Сохранение старого ВР
Язык Ассемблера в программировании информационных и управляющих систем 1 87
MOV
PUSH
LES
LDS
MOV
MOV
SUB
BP,SP ; Определение базы зоны параметров
DS ; Сохранения сегмента данных
DI,ProtPtr[BP] ;
SI,VcbPtr[SI]
CL,DS:RootLn[SIj
СН,DS:MrphCtr[SI]
CL,DS:ModLn[SI] ;
Подготовка
Подготовка
Вычисление
частей
адреса
адреса
аргумента
образа
длин сопоставимых
MOV
LEA
MOV
CMP
JA
PUSH CX
PUSH SI
PUSH DI
CMPSB
POP DI
POP
POP
DX,DI
DI,Root[DI]
AX,ArgLn£BP]
CL,AL
NotUnif
Подготовка длины аргумента
Сохранение образа
Сохранение адреса аргумента поиска
REPZ CMPSB ; Использование группового ресурса сравнения
Восстановление адреса аргумента поиска
SI
СХ
JNZ NotUnif
CALL CaseCmp ; Сравнение до первого совпавшеного
; падежа
NotUnif
АХ,DX ; Формирование сегментной части указателя
DX,DS
; Восстановление сегмента данных
Lret:
:MOV
MOV
POP DS
POP BP
RET
NotUnif:MOV AX,0
MOV DX,AX
JMP Lret
VcbCmp ENDP
Формирование сегментной части указателя
При решении этой задачи нужно учитывать еще неоднозначность задачи
из-за возможности одинаковых словоформ для разных основ, что надо
учитывать при построении программы поиска, использующей процедуру VcbCmp.
Для информационного поиска в линейной структуре необходимо
определить операции просмотра и сравнения, причем первая операция необходима
чаще всего, если элементы внутреннего представления имеют варьируемую
длину. Важнейшей процедурой является поиск идентичного элемента с его
контролем по маске, который может выполняться фрагментом, записанным для 32-
битового процессора.
MOV EDX,006FFFFFh
MOV EBX, Ctrl ling
AND EBX,EDX
LODSD
; Загрузка маски
; Загрузка контролируемого образа
; Загрузка очередного элемента
I 88 Глава 8 Работа с внутренними структурами данных информационных систем
AND EAX,EDX
CMP ЕАХ,ЕВХ ; Сравнение с учетом маски
JE Lfound
CMP ESI,ECX ; Контроль границы просмотра
JB LB
; Не найдено
Lfaund:
При построении полного комплекса программ необходимо обобщить
этот фрагмент на случай сопоставления групповых интегрированных образов и
групп раздельных образов поиска для одного и того же текста.
Для изучения вариантов компактного построения системы управления
ИБ, предназначенной для использования как в главной, так и внешней памяти
операционной системы (ОС), способной функционировать в разных режимах
сохранения данных (оперативном, архивном, постоянном) и режимах
обновления ИБ с управляемыми уровнями приоритетности доступа к данным с
использованием разных информационных носителей (ОЗУ, ПЗУ, дисковые
накопители, стримеры и др.)- Необходимо отдельно определить базовые операции,
направления информационных преобразований и метод управления
соответствующей виртуальной машиной, рассматриваемой в следующей главе.
ИБ похожих типов могут применяться в разнообразных системах
анализа объектов, управления и автоматизации проектирования. ИБ систем
контроля и диагностики либо автоматизированного управления корректностью
функционирования и надежностью, может быть рассмотрена как ИБ,
распределенная между целевыми и инструментальными компьютерами. Она нуждается в
надежном сохранении и восстановлении информации, утраченной в
приспособлениях и узлах комплекса.
Структуры навигации и гипертекстовые структуры строятся по тому же
принципу, что и надстроечные структуры для таблиц, однако программы,
которые их обслуживают, как правило, обладают свойствами запоминания
маршрутов и трасс просмотра, но нужно обеспечить специальную организацию
стека или следа трассы просмотра данных.
Краткие итоги
Рассмотренные способы и приемы построения информационных таблиц,
а также методы работы с таблицами позволяют существенно повысить
эффективность информационных систем и встроенных информационных баз
системных программ. Использование в этих структурах данных таблиц, адекватных
системе команд инструментального процессора позволяет в несколько раз
повысить скорость работы инструментальных систем программирования.
ГЛАВА 9
ОБРАБОТКА УПРАВЛЯЮЩИХ ДАННЫХ
ИНФОРМАЦИОННЫХ СИСТЕМ
В том разделе рассматриваются ал/оритмы и примеры
использования метода виртуальных машин для решения задач
сиитактческой и семантической обработки в ннс/юрмацнон-
ных и управляющих системах, а также системах
искусственною интеллекта
Фактическое знакомство пользователей и программистов
компьютерной техники с методом виртуальных машин (ВМ) начинается с изучения
алгоритмических языков процедурного типа и принципа программного управления
вычислительными машинами. Исторически метод ВМ возник при первичной
реализации языков программирования и термин "виртуальная машина"
рассматривается как кажущаяся, или программная реализация машины.
Процедурно-ориентированные языки программирования строятся на
принципе последовательного выполнения цепи операторов, команд либо
директив. Их базовые операции, операторы, команды и директивы можно
классифицировать по трем группам:
• безусловные операторы вычислений и преобразований;
• операторы разветвлений процесса обработки и передачи управления;
• операторы организации циклической обработки.
Общую структуру ВМ составляют информационная, операционная,
управляющая и коммуникационная компоненты. Элементы первичной ВМ,
которую традиционно называют машиной фон-Неймана, включают:
• память для сохранения программ и данных, представляющая
информационную компоненту;
• указатель текущей операции, который является сердцевиной
управляющей компоненты и изменяется путем подсчета номера оператора;
• блок управления операциями, выполняющий функции операционных и
управляющих компонент, а вместе с интерфейсным оборудованием и
коммуникационных компонент, обеспечивающих связь ВМ с внешним
миром.
Архитектуру подобной машины мы рассматривали в главе 2 на примере
процессоров семейства ix86.
I 90 Глава 9 Обработка управляющих данных информационных систем
9.1. Типы управления в виртуальных машинах
информационных и управляющих систем
Дальнейшее развитие архитектуры ВК привело к включению таких
дополнительных элементов, как стек и стековый указатель, которые позволяют
эффективно хранить промежуточные данные и трассы вложенных вызовов
подпрограмм. Стек оказался хорошо приспособленным для обработки
математических выражений, вложенных подпрограмм, синтаксического анализа, что
способствовало дальнейшему широкому использованию процессоров со
структурой фон-Неймана. Однако такая структура совсем непригодна для
выполнения параллельных вычислений и нуждается в дополнительном
усовершенствовании для применения к решению более сложных и многосвязных задач.
Например, обратные задачи в системах программирования и специальные задачи
искусственного интеллекта требуют хранения значительных объемов
семантической информации, даже, если методы их решения, ориентированы на
выполнение последовательности команд.
Таким образом, базовая ВМ способна выполнять прямую
последовательность вычислений по командам и ее действия легко проектируются на
систему команд современных процессоров, то есть ее система операций
практически естественно интерпретируется современными процессорами
последовательного типа.
Как вытекает из материала предьщущих глав реализация гибких
информационных и управляющих систем требует тщательной классификации и
систематизации применяемых в них методов и средств. Построение рациональной
архитектуры и компактного множества операций для получения удобной
модели вычислений при решении задач составляет основу проектирования
современной программной системы. С позиций методов ИС решение любой задачи
может быть представлено как поиск оптимального варианта решения или
управляющего воздействия.
Синтаксический и лексический анализ как элемент базовой функции
сравнения в информационных и управляющих системах требует расширения
функций внутреннего управления и делает перспективным использование
нескольких режимов, которые определяют направление изменения состояния ВМ:
• прямые информационные преобразования при синтаксическом анализе
и семантической обработке;
• обратные преобразования внутренней формы программ к форме
исходного текста для отображения программ с размещением указателей на
синтаксические ошибки;
• обратные преобразования внутренней формы спецификаций языка
программирования с целью формирования подсказок пользователю, как по
поводу коррекции программ, так и по поводу преобразования
формальных спецификаций действий в последовательность команд;
• воспроизведения специфицированных данных с информацией об их
возможном использовании.
По вышеприведенным причинам возникает потребность:
Язык Ассемблера в программировании информационных и управляющих систем 191
• в регламентации использования стека;
• в обобщенном кодировании типов данных, либо в использовании так
называемых теговых структур, которые определяют тип данных в
ярлыке рядом с их кодами;
• в управлении режимами либо направлениями работы ВМ.
На уровне программ синтаксического анализа можно отметить такие
режимы или направления работы ВМ:
• прямой анализ входных данных D по управляющим структурами С с
преобразованием в универсальную внутреннюю форму /;
• обратное воспроизведение входных данных D по внутренней форме / с
указателями ошибок М;
• подсказки коррекции С анализируемых текстов через воссоздание
вариантов структуры управляющей последовательности по пути
синтаксического разбора Т\
• подсказки семантического пути развития анализируемой си< i
данной цели G через воспроизведение возможных вариант - ;
вания ранее определенных объектов пользователя О.
Если первые два типа обработки выполняются обычно на том или ином
уровне практически во всех компилирующих и интерпретирующих системах, то
третий тип в лучшем случае ограничивается только выдачей ожидаемых
ключевых слов либо разделителей. Хотя некоторые синтаксические анализаторы
позволяют определить все возможные варианты построения языковой
конструкции и для дальнейшего повышения уровня автоматизации необходимо
лишь определить метод и соответствующую форму воспроизведения.
Четвертый режим обработки нуждается в дополнительной информации
в виде семантических описаний объектов пользователя в форме встроенных ИБ
или БД периода проектирования и может послужить для контроля
соответствия разрабатываемых программ их формальным спецификациям.
Еще более сложные формы ВМ приобретают в процессе построения
систем искусственного интеллекта (СИИ). Сложность, в первую очередь,
обусловлена сложностью обрабатываемых данных: многомерностью образов и их
воспроизведения в общем случае, который приводит к необходимости более
сложного сопоставления образов, чем в случае синтаксического анализа в
лингвистических системах. Для поиска образов по заданным эталонам необходимо
выполнить более сложные операции, чем простые сравнения, такие, как например,
в графических трехмерных системах:
• позиционирование к возможному образу либо предварительная
фильтрация ситуаций;
• преобразование координат для упрощения сопоставления объектов;
• повороты для совмещения определяющих направлений;
• масштабирование для приведения в соответствие размеров эталонов и
анализируемых объектов;
I 92 Глава 9 Обработка управляющих данных информационных систем
• определение степени и границ соответствия объектов и эталонов по
разным направлениям и их комплексам;
• работа на нескольких разных уровнях детализации анализируемых
образов и эталонов;
• возможность возвратов по правилам анализа для поиска
альтернативных вариантов решений.
Обобщенные структуры данных ВМ строятся следующим образом.
Входные и выходные данные воссоздают комплексные картины каждой
проблемной области, представленные в виде одномерной либо многомерной
последовательности и пригодные для воспроизведения в форматах воспроизведения
предметной области. Составлять такую композицию должны данные разных
типов, которые определяют элементы общей картины модели проблемной
области.
Нужно систематизировать способы представления структурированных
данных информационных систем в соответствии с базовыми требованиями к
обобщенным структурам данных ВМ:
• гибкость - отсутствие либо минимизация ограничений на структурные
количественные и качественные манипуляции с данными;
• технологичность - простота и скорость обработки данных в выбранной
системе кодирования;
• информативность - предельная полнота описания при минимальной
избыточности данных.
Как логическое следствие из этих требований обеспечиваются два
базовых свойства: универсальность и мобильность, определяющие возможность
использования структур без концептуальных переделок для воспроизведения
данных из разных проблемных областей на любой аппаратной платформе.
Требование гибкости приводит к целесообразности определения типов
перед определением значений элементарных данных, то есть использования
управляющих структур так называемых тегов возле значений данных. Может
быть целесообразным совместить использование описаний форматов с данных
и управляющих инструкций в период выполнения программ.
Управляющие данные должны быть оформлены в виде древовидной
структуры с возможностями прямого и обратного позиционирования и
адекватной связи с эталонами поиска и обеспечивать предельно возможную
скорость поиска в последовательности эталонов.
Если в первичных теоретических построениях процессов выполнения
синтаксического анализа разработчики избегали возвратов к ранее
обработанным входным данным по элементам управляющих данных, то при
программной реализации интерпретации логики предикатов в языке Prolog возвраты по
пути предыдущей обработки для получения дополнительных решений стали
нормой обработки. Поэтому при разработке ВМ этого типа целесообразно
предусмотреть рациональное сохранение путей для повторного анализа с
сохранением позиций управляющих и информационных структур в стеке ВМ СИИ, а
Язык Ассемблера в программировании информационных и управляющих систем 1 Q3
предопределенных переменных, в так называемом следе логического
доказательства.
Направления работы ВМ в обобщенных терминах определяются такими
режимами:
• режимы обработки по интерпретационной семантике:
♦ интерпретация управляющих данных для обработки данных
оперативной информации (режим фон-Неймана);
♦ интерпретация синтаксических признаков оперативных данных с
сохранением путей для обработки рекурсивно вложенных
конструкций (режим языковой синтаксической обработки);
♦ формирование комплекса возможных решений и выбора
оптимальных режимов (режим рекурсивной реализации логики
предикатов);
• режимы обработки по синтаксически-ориентированному
воспроизведению входных текстов программ:
♦ воспроизведение деклараций и спецификаций;
♦ реконструкция прочих лингвистических объектов;
• режимы объяснений и подсказок:
♦ воспроизведение текстов, объясняющих синтаксис и семантику
общих лингвистических средств СИИ;
♦ генерация объяснений синтаксиса и семантики структур данных
пользователя.
Реализация последнего режима требует, как минимум, наличия модели
проблемной области в виде ИБ с объяснениями информационных формализмов
пользователя. Это с другой точки зрения открывает путь к построению
формальной либо аналитической модели проблемной области, благодаря которой
в близкой перспективе можно будет решать интеллектуальные задачи
автоматизированного построения программных средств и такие задачи обобщенной
формализованной проблемной области, как:
• верификация путей разработки и построения алгоритмов по
формальным спецификациям проблемной области;
• синтез программ по спецификации целей и объектов проблемной области.
Методика построения моделей проблемных областей должна строиться
на общих принципах, начиная с построения модели пространства либо системы
координат проблемной области.
Важным элементом построения систем на базе методов инженерии
знаний является перманентная многоуровневость таких систем. При решении
задач распознавания образов почти необходимой частью алгоритмов становится
уточнение результатов распознавания с помощью результатов, полученных на
соседних уровнях СИИ. Так, даже для синтаксического анализа текстов
необходимы действия, использующие уровни морфологического, синтаксического и
семантического анализа предложений.
7 В И. Пустоваров
I 94 Глава 9 Обработка управляющих данных информационных систем
9.2. Архитектура и система операций виртуальных
машин в инженерии знаний
Из вышеприведенного следует, что обобщенная схема ВМ для
перспективных методов искусственного интеллекта нуждается в организации связей с
предисторией обработки с помощью стеков (stacks), в которых запоминается
путь возврата к анализу внешних объектов. Следы (trails) используются для
восстановления состояния системы после получения предварительного
результата обработки (позитивного или негативного). Структуры связывающие
параллельные описания состояния модели проблемной области на разных
уровнях решения задач необходимы для сопостовления результатов.
Основу системы команд ВМ должны составить операции:
• управление (последовательностью команд, переключением моделей,
состоянием стеков, состоянием следов, связей параллельных моделей
данных);
• координатных преобразований (позиционирование, масштабирование,
повороты координат и отображений);
• элементарных математических действий, включая анализ
маскированием, и вхождения в наперед заданные диапазоны;
• взаимодействия с внешней, относительно ВМ, средой.
Машинные команды такой ВМ могут быть построены по так
называемому принципу горизонтального программирования, широко используемому
при реализации микропрограмм. Этот принцип использует запись ряда команд
в одной строке и обеспечивает возможность одновременного использования
разнородного оборудования ВМ при условиях ее реализации на
микроэлектронном уровне.
Обобщенное описание ВМСИИ должно включать следующие
архитектурные элементы для реализации базовых операций и управления
последовательностью их выполнения:
• управляющий блок, включающий регистр состояния и сохраняющий:
♦ код режима работы CR;
♦ текущую позицию в последовательности или дереве
управляющих кодов IP;
♦ указатель практически неограниченного стека SP для сохранения
вложенных вызовов процедур и хранения промежуточных
результатов;
♦ указатель следа ТР для возврата к анализу ранее
нерассмотренных возможных вариантов решений;
• операционный блок, реализующий систему команд ВМ с описанными
выше свойствами;
• коммуникационные связи для ввода анализируемых данных, а также
вывода и отображения результатов, которые, в принципе, могут
использовать команды и подпрограммы обмена инструментального процессора
(в нашем случае ix86).
Язык Ассемблера в программировании информационных и управляющих систем I 95
Структуры данных определяют область эффективного применения
ВМСИИ. Чтобы сохранить универсальную применимость процессора для
традиционных задач, нет смысла отказываться от традиционных в языках Ассемблера
численных, символьных и адресных данных. Традиционные численные типы
данных и методы их обработки стандартизованы и обеспечивают достаточную
гибкость за счет традиционного использования форматов разной точности.
Другой вариант повышения гибкости состоит в создании тегов
(ярлыков) для структурированных данных. Традиционные структуры данных языков
программирования высокого уровня прекращают существование в процессе
компиляции, превращаясь в относительные адреса структур и объектов
машинных команд. Однако при формировании многих данных СИИ имеется
возможность, а иногда и необходимость сопровождать их служебной информацией о
характере данных, в соответствии с форматами, применяемыми
пользователями. СИИ реального времени обрабатывают контролируемые потоки
разнообразных данных, предоставляющих информацию о модели предметной области
и объектах исследования.
При использовании CISC-стратегии (complex instruction set) развития
процессоров в проект ВМСИИ следует включить операции для кодово -
структурированных представлений данных, обеспечивающих сравнения по
распознаванию типа данных, сравнения по сопоставлению разных экземпляров данных
и воспроизведения для представления перед потребителем. На этой базе могут
быть реализованы усовершенствованные действия для анализа языковых
представлений информации и распознавания образов.
Набор функций ВМСИИ в первом приближении могут составлять
операции, систематизированные по возрастанию их сложности:
• традиционные операции над числами и строками, например, на базе
операций процессоров семейства ix86;
• операции над битовыми данными,
• операции перенумерованными или двоично кодированными данными;
• операции над композициями и структурами данных;
• обобщенные операции сравнения, сопоставления и классификации;
• анализ математических и управляющих выражений для критериев поиска;
• операции сканирования или последовательного просмотра для
выделения элементов сопоставимых с эталонами (элементарных событий
управляющих и информационных систем);
• операции группового сравнения и сопоставления моделей объектов.
Проектирование ВМСИИ такой сложности должно сочетать
использование методов проектирования аппаратуры с методами моделирования и
интерпретационной программной реализации ВМ, в которых ключевым
моментом является формат представления структурированных данных.
I 96 Глава 9 Обработка управляющих данных информационных систем
9.3. Архитектура языковых виртуальных машин
и реализация ее элементов
Главная функция системных обрабатывающих программ - выполнение
подготовки программ для их дальнейшего выполнения на ЭВМ. Входные
данные для построения программ готовятся преимущественно, как тексты на
языках программирования, языках запросов и директив. В наше время с быстрым
развитием СИИ распространяются и находят применение языки спецификаций
и языки, которые приближаются к естественным.
Входные данные системных обрабатывающих программ
преимущественно являются управляющими, предназначенными для последующего
управления вычислениями. Они готовятся в форме текстов на довольно гибких
языках, предназначенных для разнообразных применений.
Для выходных и внутренних данных системных транслирующих
программ используются специальные структуры данных, приспособленные для
нужд дальнейшей обработки информации. Выходные структуры данных
транслирующих систем обычно строятся в форматах объектных модулей, которые
начально определяли принадлежность к формам целевой информации на
выходе компилятора, а в наше время приняло форму относительного стандарта
внутреннего представления объединяемых модулей. Внутренние данные
транслирующих системных программ с одной стороны должны рационально
систематизировать и упорядочивать воспроизведение оперативной информации,
поступающей в качестве входных данных, и представление управляющей
информации об особенностях входного языка. Кроме этого они должны обеспечивать
достаточную быстроту работы программ семантической обработки:
оптимизации, генерирования кодов и интерпретации.
Общая структура обрабатывающей программы информационной или
управляющей системы включает:
• лексический анализ, который выделяет и преобразует лексические
элементы входного языка во внутренюю форму представления;
• синтаксический анализ либо проверку допустимости входных данных;
• семантическую обработку, выполняемую либо во время трансляции,
либо на этапе эксплуатации программы. Наличие нескольких
преобразующих функций, выполняемых наперед в определенной
последовательности, определяют возможность нескольких вариантов
многопросмотровой организации системных обрабатывающих программ. Простейший
вариант последовательности работы компилятора, как отмечалось в
предыдущих главах, состоит в последовательном преобразовании
лексическим анализатором программы на входном языке к ее внутренней
кодированной форме. В дальнейшем файл кодированной формы
преобразуют в файл внутренней формы, пригодной для быстрого анализа,
компрессии этого файла оптимизирующими программами и генерации
объектного кода.
Остальные варианты последовательности обработки возникают из
возможностей обработки текстов по отдельным частям, называемым предаю же-
Язык Ассемблера в программировании информационных и управляющих систем I 97
ниями. Для предложения можно построить последовательность действий,
аналогичную той, которая выше была представлена для полных файлов. Таким
образом, можно строить достаточно широкий спектр комбинаций функций в
транслирующих системах.
Объектный файл стали использовать с появлением модульного
программирования, которое в отличие от монолитного программирования задач
открыло пути использования предыдущего опыта программирования. Это дает
возможность распределения работы по программированию большого проекта
между несколькими программистами и для эффективной трансляции
стандартных функций. Большинство современных компиляторов строит объектный
файл как промежуточный, для последующего объединения с другими
модулями. У большинства современных операционных систем они имеют расширения
.obj и строятся вокруг базовых стандартных элементов этого файла:
• элементов двоичного текста кодов в машинной форме, с кодами
машинных команд, констант и инициирующих данных программы;
• элементов спецификации глобальных имен и внешних ссылок, в которых
определяется образ имени и его внутренние характеристики в модуле;
• элементов спецификации сегментов памяти, используемых при решении
задач, включая образы их имен и основные характеристики;
• элементов спецификации переместимости, которые определяют правила
изменения значений адресов в зависимости от типов данных сегментов
определения и варианта объединения
• логических сегментов в единый физический сегмент;
• элемента разграничителя модулей, в котором определяется адрес
входной точки либо первой выполняемой команды модуля.
9.3.1. Программирование лексического анализа
Главная цель лексического анализа - превратить неструктурированную
последовательность входного текста к структурированной форме. Другими
словами необходимо разделить входной текст на лексические единицы и
классифицировать их. При этом нужно придерживаться основного требования к
системным программам и их элементам - максимальной скорости обработки.
Лексический анализатор, иногда называемый сканером, представляет
собой часть языковой ВМ, читающей буквы входного текста для Синтеза таких
атомарных конструкций как имена (идентификаторы), служебные слова,
численные константы, разделители, такие, как знаки пунктуации и знаки
арифметических операций, называемые лексемами. В отличие от синтаксического
анализа он проверяет локальное состояние при вьщелении атомарных объектов и не
требует глубокого анализа связей.
Перечислим наиболее весомые аргументы в пользу отделения
лексического анализа от синтаксического:
1. Синтаксис атомарных обозначений описывается очень простыми
грамматиками с простой техникой реализации.
1 98 Глава 9 Обработка управляющих данных информационных систем
2. Сокращение времени синтеза атомов распознавания за счет эффективной
реализации операций сопоставления по маскам и поиска опорных элементов
входного информационного потока, таких как разделители в текстовой информации.
3. Возможность разрешения на этом уровне ряда неоднозначностей и
поиска многовариантных решений для последующего уточнения на уровне
синтаксического анализа.
4. Возможность приближения к работе с внутренними формами
естественного языка и представления многомерных образов.
5. Переход к новой более компактной системе внутреннего
представления данных, позволяющей сократить затраты на дальнейшую обработку.
Грамматика языка [18] обычно связывается с синтаксическими
правилами представления языка и включает в качестве компонент алфавит языка V,
включающий словарь терминальных символов Т, совокупность правил
подстановки Р и начальный порождающий символ Z для выхода рекурсивного анализа.
С позиций теории грамматик задачи сканера могут быть
сформулированы через несколько грамматик, определяющих распознаватели лексем разных
классов. Нетерминальные понятия грамматики G, которые мы выделяем
жирным курсивом, являются важными элементами языка. Среди них базовые
грамматики можно определить как грамматики допустимых синтаксических
структур (разделитель, константа, ключевое слово, имя) и автоматную грамматику,
опирающуюся на текущее представление состояния анализатора. Такой подход
называют расслоением грамматик по этапам анализа, чтобы избежать
непосредственного использования чрезмерно сложной полной грамматики
анализируемого языка, приводящей к значительным трудностям при стремлении
получить скоростную обработку. Ограничения на правила грамматики упрощают
реализацию анализаторов и систем программирования.
При лексическом анализе базовым терминальным символом, на
котором заканчивается процесс анализа, является буква, что вытекает из
традиционного определения именующих объектов, как последовательности букв и
цифр, начинающихся с буквы. Но наверно еще более важным фактором
является необходимое во всех языках наличие разделителя или пробела между
лексемами. Сам этот пробел и недопустимые буквы тяжелее всего распознать
традиционными средствами грамматики.
Таким образом, для достижения цели лексического анализа
целесообразно идти одним из следующих путей:
• грамматический анализ по дереву с построением полного множества
возможных решений (фраз грамматики) через нисходящий разбор, для
которого сначала следует определить все возможные варианты анализа
по предыдущему контексту;
• грамматический разбор путем восхождения к корню грамматики,
которая опирается на поочередный анализ букв;
• предварительный поиск разделителя для следующей лексемы с
последующим корректным грамматическим разбором предыдущей лексемы.
Язык Ассемблера в программировании информационных и управляющих систем I 99
При сканировании наиболее мощным считается метод классификации
букв текущего контекста, в котором важнейшим функциональным элементом
является определение классификационного типа буквы и его внутреннего
кодового представления. К тому же он позволяет сравнительно легко определить
недопустимые и неклассифицированные буквы входного информационного
потока.
Типовая классификация букв, имен и ключевых слов включает
обозначения - буква имени \ цифра. Классификация букв для констант - цифра \
определитель типа константы \ разделитель целой и дробной части \ разделитель
экспоненты. В связи с тем, что входной информацией лексического анализатора
является последовательность букв, распознавание их типов можно вести не
только через синтаксические деревья, но и через поочередную классификацию
букв и последующее определение локальной грамматики для каждой лексемы.
Обработка букв разных типов выполняется по разным правилам с
использованием данных о контекстной зависимости фрагментов символического
обозначения. При выполнении лексического анализа каждую букву целесообразно
преобразовать к некоторой специальной форме внутреннего представления, в
которой должна храниться информация о синтаксической допустимости
использования буквы, и о правилах их семантической обработки в процессе
преобразования лексем во внутреннюю форму. С учетом вышеизложенного и
практического опыта автора буквы можно разделить, на такие основные группы:
• разделитель лексем - это обычно знак операции языка
программирования, скобки разных форм, такие разделители элементов перечислений,
как запятая "," или точка с запятой ";" и двоеточие ":", которое чаще
всего отделяет метку;
• вспомогательный разделитель - это коды пробелов, табуляций и
переводов строк;
• буква имени - это обычная буква латинского алфавита и некоторые
специальные символы, например, подчеркивание "_" (отметим, что при
условии знания организации управляющих таблиц сканера несложно
допустить использование в имени букв кириллицы);
• цифра ::=0\ 1| 2| 3| 4| 5| 6| 7| 8| 9;
• шестнадцатиричная цифра ::= а| b| c| d| e| f| A| B| C| D| E| F;
• определитель константы - это чаще всего ведущий ноль либо цифра в
начале константы, либо кавычки или апострофы в начале и в конце
константы типа строки;
• определитель типа константы - это некоторая зарезервированная буква
латинского алфавита, которая определяет основу системы счисления для
констант или тип символьной константы;
• разделитель целой и дробной части - это обычно точка ".", которая
отделяет целую часть числа от дробной;
• разделитель экспоненты - это обычно латинская буква "е" или "Е",
которая отделяет мантиссу от показателя экспоненты.
200 Глава 9 Обработка управляющих данных информационных систем
При анализе текстов на естественных языках особую роль играет
распознавание заглавных и строчных букв, гласных, согласных и таких знаков, как
мягкий, либо твердый знак и апострофы.
Из приведенной классификации букв для программ лексического
анализа видно, что некоторые из букв могут определять несколько функций
опознавания, и при условиях скоростной обработки целесообразно для всех их
построить внутреннюю форму, пригодную для быстрой классификации. Такая
форма должна соответствовать следующим требованиям:
• возможность кодирования всех классификационных признаков,
необходимых в языке, с которого транслируется программа;
• желательно тратить на кодирование буквы код полей или одно поле
фиксированного размера;
• желательно иметь эффективные методы анализа полей каждого из
типов;
• семантические характеристики целесообразно представлять в
естественном виде, пригодном для дальнейшей обработки;
• обеспечить по возможности легкое разделение полей на составные части.
Для целей внутреннего кодирования используют такие стандартные
типы данных языка С, как перенумерованные (enumerated) данные, битовые
структуры данных. В качестве операционных ресурсов применяют условную
передачу управления по маскам или по соответствию значения лексемы
заданному интервалу и передачу управления или обработку с использованием
численных индексов.
Наиболее тонкой работой надо считать оптимальное определение
множества значений перенумерованного типа как определения координаты
проблемной области классификации типов лексем Поле признака типа буквы
удобно определить через такой перенумерованный тип Turbo Ассемблера:
ENUM Itrtype { dgt,
ltrhxdgt,
ltrtpcns,
ltrnmelm,
dlmbrlst,
dlmunop,
dlmgrop,
dlmungr
цифра
буква - шестнадцатиричная цифра
буква - признак типа константы
буква - элемент имени
разделители - типа скобок и
разграничители элементов списков
разделители операций в одиночной
форме и вспомогательные разделители
элемент группового разделителя
разделители, используемые, как в
одиночной, так и групповой формах
Такое определение типа в Ассемблере определяет только обозначение
внутреннего кодового представления и может использоваться в программах
как имя только при пересылке и проверке значений. Для представления данных
такого типа достаточно 3-х битов, поэтому рядом с ним в одном байте может
Язык Ассемблера в программировании информационных и управляющих систем 201
храниться значение либо дополнительные классификационные признаки
буквы, представленные в виде структуры Ассемблера
ltrclsf RECORD Itrtype:3,value:5;тип буквы и
/внутреннее значение
Значение может быть определено как объединение разных кодов типов
данных, которые работают при разных условиях.
Однако реализации языка С не являются достаточно совершенными,
поэтому для представления структур ltrclsf необходимо не менее двух байтов.
Таким образом, классификатор построенный с использованием таких структур
связан с грамматиками так, что множество терминальных символов находит
отображение в таблице классификаторов и допускает неоднозначность
деревьев вывода. Для констант определяются специфические алгоритмы обработки, а
имена и ключевые слова превращаются во внутреннюю форму, в которой
устанавливается связь с воспроизводимыми образами и последующими вариантами
семантической обработки.
Использование регулярных грамматик в сканерах становится
целесообразным, потому что они позволяют последовательно определять или уточнять
состояние обрабатываемой лексемы независимо от конкретного значения
очередного распознанного терминала. Большинство символов в языках
программирования относятся к одному из следующих классов: имена; служебные слова,
которые являются подмножеством имен; целые числа; однолитерные
разделители; многобуквенные разделители.
Эти символические обозначения [18] могут быть описаны такими
простыми правилами грамматики Gr[Z]:
имя ::= буква | имя буква | имя цифра
целое ::= цифра | целое цифра
разделитель ::= + | - | (|) | / ]...
разделитель ::= slash/1 slash* \ ast* | colon-
slash ::- /
ast ::= *
colon ::= :
Правила могли бы быть еще проще, но мы записываем их так, чтобы
каждое правило имело вид, свойственный для регулярной грамматики: U::= Гили
U::— VT, где Т- терминал, а К- нетерминал. Синтаксис символических
обозначений большинства языков программирования можно задать в этой форме.
Следовательно, целесообразно найти эффективный способ разбора
предложений регулярной грамматики, для чего выполним разработку эффективной
схемы анализа.
Чтобы облегчить распознавание предложений грамматики Gr[Z],
построим диаграмму состояний. В таких диаграммах каждый нетерминал
грамматики Gr[Z] представлен узлом либо состоянием, кроме того, определяется
начальное состояние S (считается, что грамматика не включает нетерминал S).
Каждому правилу Q ::= Тв Gr [Z] соответствует дуга с пометкой Г, направлен-
202 Глава 9 Обработка управляющих данных информационных систем
ная от начального состояния S к состоянию Q. Каждому правилу Q ::= Rt
соответствует дуга с пометкой Г, направленная от состояния R к состоянию Q. Мы
используем диаграммы состояний, чтобы различить или разобрать цепочку х
следующими командами.
1. Первым текущим состоянием будем считать начальное состояние S.
Начать с самой левой буквы в цепочке х и повторять шаг 2 до тех пор, пока не
будет достигнут правый конец х.
2. Проанализировать следующую букву х и продвинуться по дуге,
обозначенной этой настоящей буквой, переходя к следующему состоянию.
Если при каком-то повторении шага 2 такой дуги не появится, то
цепочка х не является предложением и разбор заканчивается. Если мы достигаем
конца х, х - предложение тогда и только тогда, когда последнее текущее
состояние есть Z.
В этих действиях можно узнать восходящий разбор. На каждом шаге
(кроме первого) основой является имя текущего состояния, за которым идет
буква или цифра. Символическое обозначение, к которому приводится основа,
будет именем следующего состояния.
Чтобы иметь возможность легко манипулировать с диаграммами
состояний, нам необходима дальнейшая формализация концепции в терминах
состояний, входных букв, начального состояния S, "отображения" М, которое по
заданному текущему состоянию Q и входной букве Т указывает следующее
текущее состояние и заключительное состояние.
Реализация конечного (детерминированного) автомата (КА) через
матрицу переходов требует хранения в памяти компьютера кода состояния (Si,...,
Sn) и классификаторов входных букв (7i,..., Tm). КА можно представить
матрицей В, состоящей из n*m элементов. Элемент B[ij] вмещает число к - номер
состояния Sk, такого, что М [5/, 7}] = S*. Можно договориться, что состояние S\, -
начальное, а список заключительных состояний представлен вектором. Такую
матрицу иногда называют матрицей переходов, поскольку она указывает,
каким образом происходит переключение из одного состояния в другое. Другим
способом представления может быть списочная структура. Представление
каждого состояния с к дугами, которые выходят из него, занимает 2/:+2 слов.
Первое слово - имени или идентификатора состояния, второе - значение /с. Каждая
дальнейшая пара слов содержит терминальный символ из входного алфавита и
указатель на начало представления состояния, в которое КА переходит по
полученной букве.
Используя эти классы, мы теперь можем построить диаграмму
состояний. Эта диаграмма имеет одну дополнительную особенность - возврат, на
который нужно обратить особое внимание. Пусть на вход поступают буквы
.XOR. (операция сложения по модулю 2 в языке Fortran, причем с точки
начинается, как минимум, одно ключевое слово. Возникает вопрос, является ли
.XOR. одним обозначением или это три разные обозначения ".", "XOR" и "."?
Мы не определим это до тех пор, пока не пересмотрим список символических
обозначений. Если .XOR. не является символическим обозначением, нам прий-
дется вернуться обратно к начальной букве с тем, чтобы начать сканирование
Язык Ассемблера в программировании информационных и управляющих систем 203
сначала. Отметим, что мы возвращаемся обратно также и потому, что
игнорируются смысл букв. И дело не только в том, что пришлось бы копировать и
"хорошие" буквы, которые должны повторно сканироваться. Обусловлено это
тем, что при распознавании символа в любое время может произойти
переключение состояния в другое и, таким образом, изменится смысл букв.
Поскольку в нашем случае характер возвратов строго определен,
необходимо помнить только о последней отмеченной букве на ограниченном
интервале. Как только мы формируем код обозначения, нам не приходится далее
сохранять отметку.
Системы автоматизированного построения управляющих таблиц
лексических и синтаксических анализаторов применялись, начиная от первичных до
наиболее распространенных в настоящее время LEX и YACC (yet another
compilers compiler). В общих чертах они используют представленную выше
методику построения сканера и конструктора для сканирования символических
обозначений. Синтаксис обозначений ограничен так, чтобы алгоритм
сканирования был простым и допускал быструю и эффективную реализацию. В
некоторых системах, предназначенных для генерации компиляторов,
интерпретаторов, операционных систем и т.д., может быть определено любое число классов
и в терминах этих классов можно описать любую структуру символических
обозначений, которую можно рассмотреть через КА.
Не следует думать, что из-за универсальности системная программа
обязательно будет неэффективной. Для повышения скорости в программу
включаются новые методы обработки наиболее общих символических обозначений,
например, имен, чтобы еще более сократить затраты времени на сканирование.
Определение сканера в системах автоматизированной генерации
компиляторов включает описание классов букв и описание символических
обозначений. Класс можно описать двумя способами. Первый способ позволяет указать
только буквы в коде ASCII, принадлежащие к заданному классу. Второй -
используется тогда, когда хотят показать фактическое внутреннее представление
букв.
Результатом лексического анализа будет последовательность кодов
внутреннего представления транслируемой программы. Коды должны иметь
либо фиксированный, либо плавающий формат элементов. Фиксированные
форматы, имеют некоторую избыточность кодов, как правило, позволяющую
сократить затраты на анализ внутренних кодов. Если брать за базовый формат
внутренней формы метасинтаксическую пару операнд операция, то мы довольно
легко получаем фиксированный формат, в котором в случае корректной
программы операция всегда есть в наличии, а операнд иногда может быть
фиктивным или пустым.
При разной длине элементов, имеющей место при построении цепочки из
одиночных лексем, необходимо использовать так называемые префиксные коды,
построенные методом правосторонней рекурсии и имеющие различаемые левые
части. Такие коды также часто используют для архивации данных в сжатой форме.
Рациональный выбор кодов внутреннего представления программ
должен обеспечивать минимизацию преобразований кодов для использования на
разных этапах компиляции. Одним из перспективных вариантов построения
204 Глава 9 Обработка управляющих данных информационных систем
лексического анализа для систем программирования следует считать
использование унифицированной внутренней формы представления входной
программы, пригодной для воспроизведения в любом языке программирования.
Подобный подход использовался в нескольких не очень давних версиях языка Бейсик
и в системах программирования семейства Topspeed. Однако на сегодняшний
день такое построение языковых систем скорее исключение, чем правило. С
другой стороны унифицированная внутренняя форма может послужить
основой не только для хранения ключевых слов и разделителей, но и для
установления семантического соответствия между словами и обозначениями разных
языков в программировании и лингвистике.
9.3.2, Программирование восходящего разбора
при синтаксическом анализе
На примере лексического анализатора мы снова убедились, что выбор
грамматики является делом, связанным как с определением языка, так и с
определением цели преобразований. И если грамматики ориентируют разработчика
в направлении контроля грамматической корректности, то для компилятора,
она тоже является достаточно важной, хотя и не определяющей. Более важным
является постепенное преобразование данных к формату объектных кодов.
Если главная задача лексического анализатора превратить буквенно-кодирован-
ную форму входной программы в лексемно-кодированную, то задача
синтаксического анализатора, проверив корректность лексемно-кодированной формы,
превратить ее в цепочку команд некоторой ВМ, т.е. кроме распознавания
корректности мы должны выполнить преобразования на вполне определенных
внутренних кодах.
Рассмотрим схему синтаксического разбора, в которой применяется
метод восхождения. При использовании этого метода повторяется поиск основы
(самой левой простой фразы и), которая согласно правилу U ::= и приводится к
нетерминалу U. При применении любого из методов восходящего разбора
возникает вопрос - как найти основу и выяснить, к какому нетерминалу придется
ее приводить. Рассмотрим этот вопрос для определенного класса грамматик,
называемого грамматиками простого предшествования, так как для
произвольных грамматик эта задача не разрешается.
Как же найти основу, анализируя только пару соседних символических
обозначений одновременно? Сначала нужно определить, нашли ли мы хвост
основы, а потом, продвигаясь обратно к левому концу предложения, найти
голову основы, вновь-таки принимая каждый раз решения только по двум
соседним обозначениям. То есть, перед нами возникает такая проблема - если задана
цепочка ...RS..., всегда ли R является хвостом основы, либо RS вместе могут
встретиться в основе, либо возможны иные варианты. Для быстрого
оперативного разрешения этой проблемы целесообразно предварительно исследовать
грамматику и принять решение относительно каждой пары символов Ru S.
Рассмотрим теперь два символа R и S из словаря V грамматики G. Пусть
существует предложение ...RS... На некотором этапе канонического разбора ли-
Язык Ассемблера в программировании информационных и управляющих систем 20о
бо R, либо S (или оба символа одновременно) должны войти в основу. При
этом возникают такие три возможности.
1. R - часть основы, a S нет. Эту ситуацию мы записываем как R*>S и
говорим, что R старше чем S либо, что R предшествует S, поскольку символ R
будет редуцирован прежде, чем S. Отметим, что R должен быть последним
(хвостовым) символом в правой части некоторого правила U ::=... R. Отметим
еще, что поскольку основа находится слева от S, то Одолжен быть терминалом.
2. Оба символа R и S входят в основу. Запишем это как R* = S. У них
одинаковое значение предшествования, и они должны редуцироваться
одновременно. Очевидно, в грамматике должно быть правило £/::=... RS...
3. S - часть основы, a R - нет. Отношение между ними записывается как
R <• S; можно говорить, что R младше чем S. Символ S должен быть головным
в правой части некоторого правила U.\- S...
Если формы предложения ...RS... не существует, считаем, что между
парой символов (R, S) не определено никакого отношения. Заметим, что никакое
из трех определенных выше отношений предшествования <• , •= и •> не
является симметричным. Например, из R <• S совсем не вытекает S •> R.
Любая грамматика, которую можно представить матрицей
предшествования, указывающей отношения предшествования для всех пар символов.
Элемент этой матрицы В[,у] содержит отношение между парой символов (Si,Sj).
Пустой элемент матрицы свидетельствует о том, что между двумя
соответствующими символами отношение предшествования не определено.
На первый взгляд может показаться, что тяжело найти все отношения
предшествования между символами грамматики. Создается впечатление, что
придется пересмотреть много синтаксических деревьев, чтобы найти все
отношения. Однако при практическом применении отношений предшествования
для распознавания предложений в алгоритме разбора требуется способ их
компактного представления. Обычно этой цели служит матрица Р, элементы
которой имеют значения:
P[ij]=O, если Q/ и Q/, не связаны никаким отношением;
P[ij]=l, если(2/<* Q/;
Рро]=2, если Q/ •= Q;;
P[i,j]=3, если Q/ •> Q/.
Для грамматики предшествования такое представление возможно,
поскольку в ней между любыми двумя символами определено не более одного
отношения. Сами правила грамматики должны находиться в таблице с
элементами такой структуры, которая позволяет по заданной правой части найти
содержание правила, а потом указать соответствующую левую часть. Общий
алгоритм разбора [18] работает так. Символы входной цепочки пересматриваются
слева направо и заносятся в стек S до тех пор, пока верхний символ в стеке не
получит отношение •> относительно следующего входного символа. Это
значит, что верхний символ стека является хвостом основы и, следовательно, вся
основа записана в стек. Потом ее находят в списке правил, и она в стеке заме-
206 Глава 9 Обработка управляющих данных информационных систем
няется тем нетерминалом, к которому ее следует привести. Процесс
повторяется до тех пор, пока в стеке не появится начальный символ Z и следующим
входным символом не станет конечный символ.
При описании работы распознавателя используются следующие
обозначения.
1. S - стек, в котором хранятся символы и их счетчик /с.
2. j - индекс, который используется для адресации нескольких верхних
элементов стека.
3. Анализируемое предложение - Т\ Тг...Тп.
Мы начинаем работу, когда в стеке находится разделитель
предложения, и допускаем, что такой же символ приписан к предложению 7я+1.
4. Q и R - переменные, принимающие значения символов и
используемые для хранения нескольких символов во время работы.
Заметим, что если цепочка Т\... Тк, не является предложением, алгоритм
остановится и сообщит об этом транслирующей системе. Алгоритм состоит из
следующих блоков.
1. Прежде всего, в стек заносится разделитель предложения и
устанавливается значение индекса, указывающего на первый символ.
2. Сканировать следующий символ, разместить его в R и продвинуть
значение к.
3. Если (S(/<), P) не находятся в отношении •>, то еще не вся основа
занесена в стек, поэтому нужно занести указатель R в стек и перейти к действию 2
для сканирования следующего символа.
4. Когда S(j) •> R, основа находится в стеке и начинается поиск головы
основы через продвижение в обратном направление стека, то есть поиск такого
j, когда S(j-j) <•£(/;•
5. Проверить цепочку S(j)... S(ij. Если он а не является правой частью
никакого правила, то работа закончена. Эта цепочка является предложением в
том и только в том случае, когда /с=2, S(i)-Z.
6. Если цепочка является правой частью некоторого правила, то
исключить основу из стека, разместить в стеке символ, к которому эта основа
приводится, и перейти к блоку 3, то есть, к поиску очередной основы.
В этом и других подобных распознавателях очень привлекательно то,
что не нужно одновременно хранить в памяти всю цепочку входных символов.
Символы поочередно вводятся из входного потока по одному и заносятся в сте-
к, но после редукции основы те символы, которые входили в нее, исчезают. Всю
цепочку приходится хранить в памяти только в том случае, если основа находится в
правом конце цепочки; но грамматики языков программирования никогда не
строятся подобным образом.
Матрица предшествования может занимать слишком большой объем
памяти. Если в языке 120 символов, понадобится матрица, состоящая из 120
элементов, каждый длиной не менее двух битов. Однако во многих случаях
информация, которая вложена в матрице, может быть представлена двумя
функциями/и g, такими, что из R*= S следует f(R) = g(S), из R <• S следует/(Я) <
Язык Ассемблера в программировании информационных и управляющих систем 207
g(S), а из R •> S следует f(R) > g(S) для всех символов грамматики. Это
называется линеаризацией матрицы. Если функции/и g идентичны, то игнорируя
порядок и семантику выполнения операторов мы можем пользоваться лишь
одной функцией приоритета р. Таким образом, мы можем уменьшить объем
нужной матрицы с п2 до 2« или даже п ячеек.
Следует отметить, что эти функции неединственны - если для данной
матрицы найдется хотя бы одна пара функций/и g, для той же матрицы
существует бесконечное количество таких функций. Но есть много матриц
предшествования, для которых эти функции не существуют. Нельзя линеаризировать,
например, даже такую матрицу предшествования размером 2x2 для символов
Si и Si:
Рассмотрим несколько других алгоритмов разбора этого же типа.
Сходство всех этих алгоритмов в том, что для сведения предложения к начальному
символу, выполняется следующая последовательность шагов.
1. Найти основу х или некоторую ее разновидность.
2. Найти правило U::= х схв правой части.
3. Привести хк U и таким образом построить один куст
синтаксического дерева.
Различия у таких распознавателей сводятся к двум моментам: к
количеству и размещению символов, по которым определяется основа, и к структуре
таблиц и правилам самого распознавателя. Реализация любого распознавателя
распадается на следующие этапы.
1. Программируется общая часть распознавателя, использующая
таблицы, определяющие грамматику.
2. Программируется конструктор, который на этапе проектирования
проверяет допустимость данной грамматики и строит необходимые для
распознавателя таблицы.
3. Наконец, выполняется программа-конструктор для заданной
грамматики, а результат ее работы объединяется с общей частью распознавателя.
Таким образом, на этапе проектирования компилятора получаем распознаватель
для заданной грамматики.
В случае простого предшествования строится распознаватель с
рассмотренной выше последовательностью действий, а таблица - это матрица
предшествования с правилами в форме правил предшествования операторов и матриц
переходов.
На практике грамматика любого языка программирования
процедурного и объектно-ориентированного типа порождает цепочки символов, которые
состоят из терминалов, классифицируемых как объекты, действия, операции
(операторы) либо операнды. Важно отметить, что операнды никоим образом
не влияют на последовательность выполнения операций; принимаются во
внимание только сами операторы, что позволяет снять некоторые трудности.
208 Глава 9 Обработка управляющих данных информационных систем
StackElm
OpCode
Prty
Oprnd
StackElm
STRUC
db ?
db ?
dw ?
ENDS
Рассмотрим применение этого подхода для анализа математических
выражений. Будем использовать структурированный стек с элементами, объеде-
няющими в единую структуру внутренние представления очередной операции и
операнда (операнд может быть пустым или фиктивным). Приведем наиболее
простой вариант структуры, допускающий хранение операндов только в форме
целочисленных слов.
Внутреннее представление операции
Приоритетное число
Внутреннее представление операнда
В поле оператора представляются бинарные и унарные операторы,
круглые скобки и разделители вместе с характеристикой предшествования
(приоритетом), в том числе и фиктивный, с которого начинается и заканчивается
каждое выражение. В полях операндов фиксируется информация об именах,
идентификаторах и значениях констант в последовательности их получения.
Порядок обработки определяется функцией предшествования или приоритетом р. Шаг
работы общего алгоритма разбора выполняется таким образом (Si- верхний
оператор в стеке, Sj- входной оператор).
1. Если/?(&) ^ p(Sj), поместить Sj в стек операторам и перейти к
обработке следующего входного символического обозначения.
2. Если/?(£/) > p(Sj), вызвать процедуру семантической обработки,
которая определяется символом Si. Эта процедура выполнит семантическую
обработку и исключит из стека оператор Si, и, возможно, другие символы, а также
изменит в стеке операнды, связанные с этими символами, на результат
выполнения оператора Si.
В наиболее общем случае количество уровней приоритетов не
превышает двух десятков, но в некоторых системах предшествования
предусматривается значительно большее количество уровней приоритетов для нужд
пользователя. Версию, использующую приоритетную функцию предшествования,
называют еще стековым алгоритмом восходящего разбора. Такой алгоритм в
обобщенной структурированной форме может быть представлен следующей#процеду-
рой на языке Ассемблера:
SyntUp PROC
PUSH BP ; Сохранение старого ВР
MOV BP,SP ; Определение базы зоны параметров
; ES:DI используется для просмотра входной цепочки
XOR АХ,АХ
MOV DX,AX
LmsStiPUSH DX ; Запись фиктивного операнда
PUSH AX ; Запись фиктивной операции
CALL LexAn ; Возвращает в DX:AX - операцию, операнд
MOV BP,SP
LcmpSt:CMP DH,Prty[BP] ; Сравнение приоритетов
Язык Ассемблера в программировании информационных и управляющих систем 209
JA
CALL
SUB
CMP
MOV
JNE
POP
RET
SyntUp
LinsSt
SemProc
BP,4
WORD PTR[BP]
SP,BP
LcmpSt
BP
PROC
; Возвращает
; и сохраняет
,0 ; Контроль
в АХ результат
DX
окончания выражения
Здесь мы обращаемся к процедуре LexAn, выполняющей анализ пары
операнд операция и возвращающей результаты лексического анализа в DX:AX,
и к процедуре семантической обработки, возвращающей результат вычислений
в АХ.
93.3. Программирование нисходящего разбора
при синтаксическом анализе
Анализ методов разбора для контекстно-свободных грамматик мы
продолжим с нисходящими распознавателями или, как их иногда называют,
прогнозирующими или целенаправленными распознавателями. В этих названиях
нашел отображение способ работы таких распознавателей и способ построения
синтаксического дерева. Сначала рассмотрим в самом общем виде метод,
использующий неизбежные и многочисленные возвраты, а потом сократим
количество возвратов для некоторых грамматик. Потом изберем совершенную
форму внутреннего представления правил, чтобы построить программу с
возможностями использования рекурсивных процедур при программировании
нисходящего метода.
Алгоритм нисходящего разбора [18] строит синтаксическое дерево,
начиная с корня, постепенно спускаясь к уровню предложения. Описание
усложняется главным образом из-за вспомогательных операций, необходимых для
возврата с твердой уверенностью в том, что все возможные попытки
построения дерева были произведены. Чтобы свести усложнение к минимуму,
рассмотрим этот алгоритм с позиций использования анализирующих ресурсов. В
каждом узле уже построенной части дерева, используется по одному ресурсу.
Ресурсы, использующиеся в терминальных узлах, занимают места,
соответствующие обозначениям предложения - это настоящее нисходящие
распознаватели. Некоторому вычислительному ресурсу поручается провести разбор
предложения х. Будем использовать следующие обозначения выводов: => для
вывода правил; =>+ для вывода предложений; =>* для вывода цепочек.
Прежде всего, ему необходимо найти вывод Z =>+х, где Z - начальный символ;
следовательно, первым непосредственным выводом должен быть вывод Z => у,
где Z::= у - правило. Пусть для Z существуют правила Z::= X\, Xi,..., Xn | 7i,
У2,..., Ym I Zi, Z2,..., Zm. Сначала ресурс пытается применить правило Z\\- Х\,
Хг,..., Хп. Если нельзя построить дерево, используя это правило, он делает по-
210 Глава 9 Обработка управляющих данных информационных систем
пытку применить правило Z\\- Y\, Yi Y,,,. В случае неудачи он переходит к
следующему правилу и т.д.
Как ему определить, правильно ли он выбрал непосредственный вывод
Z => Xi, Х>,..., XnP. Отметим, что если вывод корректный, то для каких-то
цепочек х будет иметь место л" = xi, л1?,... л*п, где X =>* Л7, для / = 1,..., п. Прежде
всего, ресурс, выполняющий разбор, использующий сыновние ресурсы Mi,
которые ДОЛЖНЫ НаЙТИ ВЫВОД Х\ =>* Xi ДЛЯ ЛЮбоГО Л7, ТаКОГО, ЧТО X = Xi,... . ЕСЛИ
сыну Л//, удалось найти такой вывод, он, как и любой из его сыновей, внуков и
т.д. замыкает цепочку х в предложении х и уведомляет своего отца об успехе.
Сообщение об успехе, поступившее от сына М., означает, что разбор
предложения закончен, а такие сообщения от двух, либо большего количества сыновних
ресурсов, представляют собой неоднозначный вывод, поэтому необходимо
использовать дополнительную информацию для окончательного вывода или
продолжать анализ нескольких вариантов.
Как же действует каждый из Mil Определим его цель как терминал X.
Входная цепочка имеет вид л" = .мл*:... л*«/..., где символы xi, X2,..., хп, уже
обрабатываются другими ресурсами. Mi проверяет, сходится ли очередной
неопределенный символ / с его целью X. Если это так, он распознает этот символ и
сообщает об успехе. Если нет - сообщает о неудаче. Если цель Mi - это
нетерминал Xi, то Mi поступает таким же образом, как и его отец, проверяя правую
часть правил, которые относятся к нетерминалам.
Теперь станет понятным, почему этот метод именуется
прогнозирующим либо целенаправленным. Название ''нисходящий" происходит от способа
построения синтаксического дерева. При разборе начинают от начального
символа и спускаются к элементам предложения. Привлекательность этого метода
и его представления в том и состоит, что при его выполнении ресурсу
необходимо помнить только о своей цели, но не о целях предшественников и
преемников, а также о своем месте в грамматике и во входной цепочке.
Предшественники и преемники поочередно записываются в стеке. Действительно, каждый раз,
когда используется рекурсия, удобно работать со стеком, так как он сохраняет
не только адреса возврата, но и локальные данные предыдущих вызовов.
Рассмотренный алгоритм является глубоко рекурсивным и должен
запоминать пути для возврата на уровне управления вычислениями к родительским
вызовам и хранить почти полные комплексы аргументов для каждого вызова.
Такой подход нашел применение для логического вывода в СИИ на базе языка
Prolog, где главной целью является получение всех возможных решений, в
отличие синтаксического рассмотрения, где целью является быстрейшее
распознавание конструкций в условиях нежелательной неоднозначности.
В алгоритме, описанном выше, есть серьезный недостаток, который
проявляется, когда цель определена с использованием левосторонней рекурсии.
Если X - наша цель, а первое же правило для X имеет вид Х\\- X..., то мы
немедленно обращаемся к ресурсу, который ищет X, а он в свою очередь немедленно
обращается к следующему ресурсу и этот процесс может продолжаться
практически без ограничений.
По этой причине правила грамматики записывают с применением
правосторонней рекурсии вместо более привычной левосторонней Лучший способ
Язык Ассемблера в программировании информационных и управляющих систем 211
освободиться от прямой левосторонней рекурсии - записывать правила,
используя итеративные и факультативные (необязательные) обозначения.
Запишем правила
Е::= Е+Т\ Г как Е ::= Т+{Т} и
Т::= T*F\ T/F\Fk^k T::=F{*F\/F}
Сформулируем два принципа, на основе которых правила языка,
который включает прямую левостороннюю рекурсию, превратятся в эквивалентные
правила, использующие итерацию.
Факторизация применяется, если существуют правила вывода вида £/::=
ху\ xw\... | xz путем замены на U::= х (у\ w\... | z), где скобки являются
метасимволами.
Факторизация допустима и в более общей форме, такой, как в
арифметических выражениях. Например, если у = y\yi и w = y\wi, мы могли бы
заменить U::= х 0;| w\— \z) на U':= х (У] 0^1 w>) |... | z). Заметим, что выходные правила
U::= х\ ху мы преобразуем к виду Uv.-х (у\ Л), где Л - пустая цепочка. Когда б
не использовалось подобное преобразования Л всегда размещается как
последняя альтернатива, поскольку эта цель всегда выводится. Кроме того, что
факторизация позволяет исключить прямую рекурсию, использование этого
метода сокращает размеры грамматики и позволяет проводить разбор более
эффективно.
После факторизации в грамматике останется не более одной правой
части с прямой левосторонней рекурсией для каждого из нетерминалов. Если
такая правая часть имеется, мы делаем следующее: пусть U::= x\y\... \z\Uv -
правила, в которых осталась леворекурсивная правая часть. Эти правила означают,
что членом синтаксического класса U является х, за которым либо ничего нет,
либо следует несколько v. Тогда превратим эти правила к £/::= (х\ у\... \z) {v}.
После таких изменений мы, обычно, должны изменить и наш алгоритм
нисходящего разбора. Теперь алгоритм должен уметь обрабатывать альтернативы не
только у всей правой части, но и в ее подцепочках, должен учитывать в своей
работе существование пустой цепочки Л, должен уметь обрабатывать итерацию.
Использование итерации взамен рекурсии частично изменяет и структуру деревьев.
Мы не разрешили всей проблемы левосторонней рекурсии: с прямой
левосторонней рекурсией покончено, но общая левосторонняя рекурсия еще
остается. Таким образом, правила U::=Vx и V::= Uy| v дают вывод U =>+ Uyx.
Освободиться от этого не так просто, но обнаружить такую ситуацию возможно.
Исключим из исходной грамматики все правила с прямой левосторонней
рекурсией.
Одной из проблем, возникающих при реализации нисходящих методов,
является эффективное представление грамматики в вычислительной машине.
Для представления грамматики используется списочная структура, называемая
синтаксическим графом. Каждый узел показывает собой символ S из правой
части и состоит из четырех компонент: имени (идентификация или
размещения), определения, альтернативы и преемника.
1. Имя - это сам символ S в некоторой внутренней форме, либо адрес, по
которому узел сохраняется в памяти.
212 Глава 9 Обработка управляющих данных информационных систем
SyntNode
IdnNode
DefNode
AltNode
ChnTode
SyntNode
STRUC
dw ?
dd ?
dw ?
dw ?
ENDS
2. Определение отсутствует или равно Null, если S - терминал, иначе эта
компонента указывает на узел, соответствующий первому символу в первой
правой части для S.
3. Альтернатива указывает на начало той альтернативы правой части,
которая идет за правой частью и содержит данный узел (Null, если такого в
правой части нет). Это только для первых символов в правых частях.
4. Преемник указывает на следующий символ правой части (Null, если
такого символа нет).
Кроме того, каждый нетерминальный символ представлен узлом,
состоящим из одной компоненты, которая указывает на первый элемент в первой
правой части, которая относится к этому символическому обозначению.
Компоненты каждого узла можно представить структурой
Идентификация узла
Базовое определение узла
Указатель альтернативной ветви
Указатель узла-преемника
Базовое определение узла может включать код внутреннего
представления ожидаемой операции и тип связанного с ним операнда, а указатели -
адреса альтернативной ветви и узла-преемника.
Синтаксический граф - удобная форма записи для проведения
преобразований. В модифицированном синтаксическом графе грамматики для
указания на итерацию в узлы включается дополнительное поле, в котором
указываются специальные признаки начала и конца цепочки, замкнутые в фигурные
скобки, и ограничеия повторений. Здесь будьте внимательны, так как могут
возникать вложенные многократные итерации.
Возврат в программах разбора компиляторов - явление очень
нежелательное и лучше предотвращать возвраты. Но нужно иметь уверенность в том,
что каждая ожидаемая цель доказана. Это необходимо потому, что нам нужно
связать семантику с синтаксисом, и значит после того, как мы будем
прогнозировать и находить цели, синтаксические обозначения будут обрабатываться
семантически:
• при обработке описаний переменных имена, либо идентификаторы
размещаются в таблице обозначений;
• при обработке арифметических выражений проверяется совместимость
типов операндов. Если возврат произойдет из-за того, что прогнозировалась
неверная цель, придется уничтожить результаты безуспешной семантической
обработки, выполненной при поиске этой цели. Сделать это не очень просто,
поэтому попытаемся провести грамматический разбор без возвратов.
Для того чтобы освободиться от возвратов, в компиляторах, как
контекст, будем использовать следующее "невыведенное" обозначение входной
программы. Тогда на грамматику накладывается такое требование: если есть
Язык Ассемблера в программировании информационных и управляющих систем 213
альтернативы х| у|... | z, то множество обозначений, которыми могут
начинаться слова, которые выводятся из х, у,..., z, должны быть попарно разными. То
есть, если х =>* Аи и у =>* Bv, значит А Ф В. Если это требование выполнимо,
можно довольно просто определить, какая из альтернатив х, у или z - будет
нашей целью. Заметим, что факторизация предоставляет здесь существенную
помощь. Если есть правило U::= xy \ xz, то преобразование этого правила к виду
U::= х (у | z) помогает множеству первых символов для разных альтернатив не
пересекаться.
В некоторых компиляторах синтаксические анализаторы содержат по
одной рекурсивной процедуре для каждого нетерминала U. Каждая такая
процедура, осуществляет разбор фраз, выводимых из U. Процедуре сообщается из
какого места данной программы следует начинать поиск фразы, выводимой из
U. Следовательно, такая процедура целенаправленная или прогнозирующая и
мы считаем, что сможем найти такую фразу. Процедура поиска представляет
эту фразу, сравнив программу в указанном месте с правыми частями правил
для U и вызывая по необходимости другие процедуры для распознавания
промежуточных целей. В действительности во время этого разбора дерево строится
точно так же, как в алгоритме разбора без возвратов. Отличается только
форма записи самого алгоритма.
Для того чтобы возвратов при разборе не было, как контекст
используется единственное обозначение, следующее за уже разобранной частью фразы.
Процедуры рекурсивного спуска могут быть определены с соблюдением
следующего комплекса условий.
1. Глобальная переменная nxtsmb всегда содержит указатель на тот
символ входной программы, который будет обрабатываться. При вызове
процедуры поиска новой цели указатель первого символа, который она должна
исследовать, уже находится в nxtsmb.
2. Подобно этому, перед тем как выйти из процедуры с уведомлением об
успехе, идентификация символа, следующего за уже обработанной
подцепочкой, располагается в nxtsmb.
3. Процедура сканирования готовит указатель очередного символа
входной программы и размещает его в nxtsmb.
4. Программа нейтрализации ошибок вызывается в тех случаях, когда
выявлена ошибка. Она формирует сообщение и возвращает управление на
продолжение анализа. После возврата работа продолжается так, как будто
никакой ошибки не было.
5. Для того чтобы начать синтаксический анализ инструкции, нужно
обратиться к программе сканирования, которая поместит указатель на первый
символ в nxtsmb, а потом вызовем процедуру вывода, которая будет
рекурсивно просматривать вложенные конструкции.
При таком подходе непривычно то, что процедуры не требуют
локальных переменных. Фактически единственная используемая переменная - nxtsmb.
По существу, мы просто используем обычный стековый механизм,
связывающий процедуры во время обработки для того, чтобы имитировать стек.
Преимущества этого метода вполне очевидны. Программируя, можно реорганизо-
214 Глава 9 Обработка управляющих данных информационных систем
вать правила так, чтобы они согласовывались с процедурами. Предполагается,
что автор компилятора настолько знаком с входным языком, что может
провести реорганизацию, освобождающую от возвратов. Метод сохраняет свою
гибкость и относительно семантической обработки. С этой целью в любое
место процедуры можно включить группу команд, не откладывая семантическую
обработку до тех пор, пока будет обработана вся фраза. Мы познакомимся с
этим подробнее при анализе семантической обработки. Основной недостаток в
том, что на программирование и отладку тратится больше усилий, чем в
системах частично автоматизированной локализацией ошибок, однако это
разумный метод и используется во многих компиляторах.
Большинство компиляторов после обнаружения ошибки просто
исключает путь вывода вплоть до уровня инструкции. На этом уровне пропускается
участок входной до ближайшего символа операторных скобок (begin, end) либо
до конца предложения с текущей позиции и только потом возобновляется
обработка. Такой подход, обычно, довольно примитивен.
Методы синтаксического разбора, связанные с анализом
синтаксического дерева, имеют значительные резервы для дальнейшего распространения.
Если опираться на более детерминированный подход, в котором каждый
нетерминал в синтаксическом описании определяется как цепочка комплексов,
определяющих очередное ожидаемое символическое обозначение (expectation)
терминал 7/ или нетерминал V\ и альтернативу Аи Как терминалы могут
использоваться ключевые слова языка программирования, а как нетерминалы -
многовариантные выражения и операторы. При таком подходе сокращается
необходимость в запоминании предистории вывода, анализ терминалов реализуется
как обыкновенное сравнение обозначений обрабатываемого текста программы
и элементов синтаксического дерева во внутренней форме. Обработка
нетерминалов, как и прежде может рассматриваться как рекурсивный вызов общих и
специальных процедур.
Таким образом, необходимый функциональный минимум для
построения ВМ нисходящего разбора по синтаксическому дереву, используемому в
качестве управляющих данных или программы, состоит из операций:
• проверки ожидания очередного терминала;
• обращения к программе определения очередного терминала;
• успешный и неуспешный выход из анализа нетерминала;
• переход на альтернативную ветвь дерева в случае неуспеха сравнений и
сопоставлений;
• переход к стандартной процедуре нейтрализации ошибок в случае
неуспеха и отсутствия альтернативных ветвей дерева.
Но наибольшим преимуществом такого алгоритма надо считать
возможность легкого определения семантической обработки для каждой из
конструкций. Стек ВМ, определенный в такой форме, хранит адреса возврата к
конструкциям, из которых были выполнены рекурсивные вызовы. Другое
преимущество вытекает из того, что в каждом текущем состоянии вывода сохраняется
достаточная информация для восстановления всех альтернативных путей
вывода. Она при необходимости может быть использована для восстановления всех
Язык Ассемблера в программировании информационных и управляющих систем 215
(!) возможных путей вывода для синтаксической подсказки в случае ошибок,
либо потребности в синтаксической помощи в процессе изучения нового языка
программирования или ограниченного естественного языка. Дальнейшее
усовершенствование такой ВМ возможно после изучения другой ветви синтаксического
анализа, получившей название восходящего разбора.
Рассмотренные примеры восходящих и нисходящих алгоритмов могут
быть обобщены для достижения таких целей:
• упрощения программы и повышения скорости анализа;
• возможность использования однотипных представлений для
спецификаций любых, целей задач и программ;
• легкость взаимных преобразований внешней и внутренней формы
представления программ и спецификаций;
• выбор эффективной системы кодирования для сокращения затрат на
информационные преобразования.
Одной из наиболее эффективных реализаций является метод
переключения режима разбора на анализ по управляющему синтаксическому дереву в
случае распознавания ключевых слов и на анализ по приоритетам в случае
обработки выражений. Для этого необходимо хранить указатели на метод
обработки вместе с признаком ожидания, а также определить дополнительные
правила внутреннего представления и обработки:
• приоритеты возрастают в последовательности:
♦ конечный разделитель языка;
♦ разделители предложений;
♦ закрывающие операторные скобки;
♦ вспомогательные ключевые снова;
♦ открывающие операторные скобки;
♦ закрывающие индексные и обычные скобки, операторы и
операции;
♦ базовые ключевые слова;
♦ открывающие индексные, круглые и операторные скобки;
• при выполнении восходящего разбора в стеке сохраняется информация
о фактических операндах и операциях;
• при получении ключевых слов и открывающих скобок в стек
записываются указатели на соответствующие элементы синтаксического дерева;
• в случае ожидания в стеке сохраняется прототип ожидаемого символа и
указатель на ветвь синтаксического дерева для продолжения анализа.
Такое усовершенствование алгоритма приводит к целесообразности
модификации структуры синтаксического графа и операций ВМ для повышения
гибкости управления синтаксическим анализом:
Базовая операция узла
Базовое определение узла
ContNode
OpNode
DefNode
STRUC
dw ?
dd ?
216 Глава 9 Обработка управляющих данных информационных систем
AltNode dw ? ; Указатель альтернативной ветви
ChnTode dw ? ; Указатель узла-преемника
ContNode ENDS
Замена идентификации на базовую операцию связано с тем, что
хранение идентификации в процессе выполнения мало эффективно, а разнообразие
операций синтаксического распознания, включая работу со стеком, повысит
гибкость системы команд ВМ синтаксического разбора. При использовании
таких структур или макроопределений мы фактически создаем основу для
написания программ на уровне Ассемблера ВМ с простой трансляцией средствами
базового Ассемблера. Отсутствие в языках высокого уровня возможности
обработки в данных ссылок вперед на ранее неопределенные данные во всех
языках программирования существенно ограничивает возможности формирования
адресов передачи управления и создает для Ассемблера и макросредств еще
одну важную нишу применения.
Для сохранения операндов, определенных через символические имена, в
процессе синтаксической обработки целесообразно использовать коды,
связанные с хранением этих имен в таблицах в виде индекса или номера элемента
рядом с его типом, а констант - в виде ссылок на значение вместе с признаком типа.
Семантическая обработка в транслирующей программе требует
построения еще одной ВМ для реализации семантики языка. На данном этапе
важно отметить необходимость совместимости базовой языковой ВМ с ВМ
синтаксического разбора по представлениям вспомогательных внутренних
операндов. Особенности кодирования промежуточных операндов, а также
прагматические направления обработки более детально рассмотрим позже при
изучении семантической обработки.
9.4. Построение интерпретаторов виртуальных машин
На основании выше изложенного можно построить пять классов
функционально полных ВМ, различающихся по прагматике:
• ВМ, эмулирующие и интерпретирующие аппаратные процессоры для
целей детального изучения процессов и отладки, включая кроссовую, то
есть выполняемую на программно несовместимом компьютере;
• ВМ, реализующие языки программирования;
• ВМ синтаксического разбора;
• ВМ интерпретации логики предикатов (Prolog-машины);
• ВМ распознавания образов.
Все типы интерпретирующих программ или микропрограмм при
аппаратной реализации строятся по единой схеме, включающей последовательность
действий:
• декомпозиция кода операции по составляющим:
♦ управление операционными ресурсами, чаще всего
арифметическими и логическими;
Язык Ассемблера в программировании информационных и управляющих систем 217
♦ управление запоминающими ресурсами через тип адресации;
♦ управление стеками и трассами следов для реконструкций
процессов;
♦ управление последовательностью выполнения команд;
• интерпретация выборки операндов команды;
• интерпретация собственно операции;
• интерпретация сохранения результатов;
• выполнение манипуляций со стеками и трассами;
• изменение естественной последовательности выполнения команд ВМ.
9.4.1. Интерпретация машинных команд
Интерпретация или эмуляция машинных команд аппаратно
реализованных процессоров используется для анализа деталей выполнения этих команд
или при физической невозможности их выполнения, например, из-за отсутствия
сопроцессора при выполнении команды ESC в процессорах ix86 Такой же
подход может быть использован для интерпретации системы команд любого
постороннего процессора на любой инструментальной ЭВМ в рамках систем
автоматизации программирования и отладки, называемых кроссовыми.
Например, для представления арифметических и логических операций
базового формата можно использовать методику, основанную на директиве
RECORD для описания структуры команды.
baseCom RECORD
bf1:2=0,сор:3,bf2:1=0,dr:1,wb:l,mod:2,reg:3, rm:3
Такая битовая структура определяет двухбайтный базовый формат
команд арифметических и логических операций, в котором группы битов bfl=O и
bf2=0 классифицируют команду по этому формату; три бита сор определяют
операцию по значениям машинных кодов операции: код ADD = 0, код ADC =
2, код OR = 1, код ADC = 6, код SUB = 5, код SBB = 3, код AND = 4, код СМР
= 7; 0 в бите направления результата определяет приемник поля r/m (регистр
или память), 0 в бите wb - использование однобайтных данных, поле reg -
номер регистра одного из операндов, а поля mod и rm - модификацию регистра
памяти по таблицам, представленным в главе 2. Команду конъюнкции AND
АХ,АХ с такой структурой можно определить записью
commADD: baseCom <0f4,0,0,0f0,0,0> ; определение AND AX,AX
Эта директива создает переменную типа запись с 16-битовым значением
и структурой полей, соответствующей шаблону baseCom. Отдельные поля
записи используются как управляющая информация для разных фрагментов
интерпретации и, если с ее использованием предварительно подготовить
операнды в регистрах DX и АХ, то для интерпретации операции достаточно такой
последовательности команд
21 О Глава 9 Обработка управляющих данных информационных систем
MOV ВХ,coramADD ; Копирование записи
AND BX,MASK cop ; Выделение поля
MOV CL,9 ; Определение количества сдвигов, при
SHR BX,CL ; этом в ВХ записывается код операции *4
CALL near ptr Comlnt
и группы мини-процедур типа NEAR с командами:
Comlnt;ADD AX, DX
RET
NOP
AND AX,DX
RET
NOP
Двухбайтная команда сложения
Возврат к записи результата
Выравнивание к четырем байтам
Двухбайтная команда конъюнкции
Возврат к записи результата
Выравнивание к четырем байтам
Аналогичным путем можно составить фрагменты программы для
выполнения операций с плавающей точкой, выборки операндов и сохранения
результатов, таким же образом могут быть проверены условия в командах
условных переходов. Предварительная классификация команд может быть
выполнена с использованием индексного определения адреса перехода и разветвлений,
подобных оператору case. Такой прием используется и в большинстве ОС для
разветвления по функциям и их модификациям.
В историческом плане интересно отметить, что первичные реализации
Ассемблера фирмы Intel в части трансляции машинных команд опирались на
так называемый аппарат CodeMacro, использовавший механизм близкий к
записям. На уровне машинных операций процессоров типа IBM/360/370
использовалась команда ЕХ, выполнявшая всего лишь одну подчиненную команду,
что позволяло упростить программирование интерпретаторов.
9.4.2. Интерпретация операторов и функций
языков программирования
Базовые языки информационных и управляющих систем и СИИ по
прагматике можно классифицировать так:
• процедурно- и объектно-ориентированные языки программирования,
которые определяют последовательность действий и команд для
получения значений целевых переменных и их комплексов;
• функциональные языки, включая языки логического
программирования, определяющие целевые значения через вложенные и рекурсивные
функциональные вызовы;
• языки спецификации информационных структур в базах данных для
формализации связей при реализации разнообразных систем
преобразования информации;
Язык Ассемблера в программировании информационных и управляющих систем 219
• языки спецификации задач и ресурсов, на которых определяются модели
проблемных областей разных уровней в форме связей между объектами
и их характеристиками, ограничений характеристик разных типов и
возможные цели решения задач.
Использование любой спецификации данных открывает путь к таким
видам семантической обработки как проверка дублирования и
непротиворечивости данных в информационных системах и СИИ. Это, в свою очередь вместе
с исследованием затрат вычислительных ресурсов, дает возможность искать
оптимальные варианты сохранения и обработки информации по разным
критериям.
Использование любой спецификации ресурсов позволяет
автоматизировать перестройку транслятора с целью генерации объектных кодов для новых
целевых компьютеров. А языки спецификации задач открывают путь для
автоматизируемого доказательства корректности программ, либо их верификации,
и автоматизируемого синтеза программ по заданной цели и спецификации
проблемной области, где решается задача.
Теперь можно определить структуру ВМ, реализующей языки
программирования. При этом целесообразно построить минимизированные модели
информационной и коммуникационной компоненты ВМ, и адекватную входному
языку подмножеством операций операционную компоненту.
Минимизированная модель информационной компоненты для каждого типа данных включает
аккумулятор. А для сохранения оперативных результатов, виртуально
неограниченный стек S для сохранения промежуточных результатов и виртуально
неограниченную память М переменных с произвольным доступом для
сохранения переменных. Особо отметим, что использование стека для сохранения
временных неименованных промежуточных результатов практически прозрачно и
фундаментально разрешает задачу распределения памяти для временных
переменных, если есть возможность базированного, либо индексированного
доступа к стеку, что реализовано в процессорах ix86/87. На распределении памяти
остановимся более подробно при анализе задач оптимизации.
Работа с ВМ такой архитектуры будет простейшей, так как требует
лишь четырех реализуемых типов адресаций памяти, включая индексную. При
наличии в интерпретирующем процессоре быстродоступных регистров для
базовых типов данных именно они используются как виртуальные аккумуляторы
для данных базовых типов данных. Например, аккумуляторы данных с
фиксированной точкой в процессорах ix86 (AL, АХ, ЕАХ) и регистр верхушки стека
математического сопроцессора ix87 (st[O]>, могут использоваться в качестве
аккумуляторов численных и нечисленных данных.
Для тех типов данных, которые могут быть реализованы как отдельный
случай базовых форматов целевого компьютера можно также использовать
регистры процессора. Для тех типов, которые никак не могут быть отображены
через базовые форматы данных необходимо построить виртуальный
аккумулятор с множеством операционных процедур для выполнения операций ВМ. Для
определения операндов в стеке можно использовать специальный код
внутреннего представления верхушки стека, а для индексации достаточно одного вир-
220 Глава 9 Обработка управляющих данных информационных систем
туального индексного регистра с признаком его использования в кодах ВМ.
Для представления данных и их типов у ВМ синтаксического анализа и ВМ
реализации языка будем использовать общую структуру для обозначений
данных:
datStruc STRUC
DtType dw ? ; тип операндаf определяющий размер данного
ValPtr dd ? ; указатель на значение либо образ
datStruc ENDS
Для распределения статической и глобальной памяти можно
использовать непрерывные виртуальные сегменты памяти с определением
относительного адреса для каждой переменной, указателя, структуры и массива. Для
идентификации переменных могут использоваться либо их символические имена,
если выходной язык компилятора является Ассемблером, либо относительный
адрес в практически неограниченном виртуальном сегменте памяти вместе с
указателем, либо селектором сегмента, если выходным языком является
унифицированный для большинства операционных систем объектный файл.
Для локальной внутренней памяти в наше время используют локальную
область стека, для чего в современные процессоры включены специальные
команды. После выхода процедур и функций эти временные данные фактически
уничтожаются, а память используется для новых временных данных в других
процедурах и функциях. Динамическое выделение памяти в современных
языках программирования возлагается на функциональные и
объектно-ориентированные расширения языков программирования и могут использоваться теми же
способами распределения, что используются для глобальных областей.
В нашем случае надо зарезервировать специальные (запрещенные для
других применений) значения указателей для обозначения аккумулятора и верхушки
стека.
На первом этапе семантические программы, выполняющие часть
преобразований, что позволяет облегчить дальнейший анализ. Среди таких
промежуточных форм при реализации языков программирования выделяют так
называемую постфиксную польскую запись операторов. В этой форме операнды
сохраняют последовательность традиционной инфиксной скобочной записи, а
операции записываются в последовательности их выполнения, такие операции
требуют дополнительных затрат на префиксное кодирование и распознавание
префиксных кодов. Поэтому как конечный результат первого этапа
преобразований транслятора лучше генерировать более регулярные структуры,
например, тетрады в формате, который включает четыре поля и имеет вид:
tetrElm MACRO r
oprtn&r db ? ; следующая операция
cOprndl&r datStruc <?,?> ; текущий первый операнд
c0prnd2&r datStruc <?,?> ; текущий второй операнд
cReslt&r datStruc <?,?> ; текущий результат
ENDM
Язык Ассемблера в программировании информационных и управляющих систем 221
Такая форма удобна для использования и в более сложных связных ВМ
реализации языка, но для выбранной простой структуры ВМ более
эффективной может оказаться структура триад. В ней первый операнд совмещен с
результатом, что сокращает избыточность внутренней формы и требует
включения добавочной операции пересылки вместе с операциями трансформации
форматов данных.
Рассмотрим семантические программы, которые генерируют тетрады
для изменяемых выражений, условных инструкций, меток, переходов и циклов.
Эти знания, позволяют справиться с построением семантических программ,
полезных и для других типов внутренних представлений.
Для работы с таблицей символических имен, где хранятся все
идентификаторы программы, мы в процессе лексического анализа пользуемся
следующими функциями, описанными в формате С.
Tbelem *retrup(Tbelem *indnam), которая в таблице символических
обозначений ищет элемент с именем indnam. Адрес найденного элемента
формируется как результат, если такого элемента нет, то возвращается значение NULL.
tblelm *retrcurblk (tblelm *indnam) работает точно так, как retrup, но
используется для обработки описаний. В языке с блочной структурой эта программа
пересматривает сначала идентификаторы текущего блока переходя к внешним
блокам с учетом текущей вложенности.
Tbelem *instab (Tbelem *indnam) включает новый элемент с именем
indnam заносится в таблицу символов, а адрес этого элемента возвращается как
результат. Такие обращения к процедурам можно использовать также при
компиляции с языка Pascal и почти с любого другого языка. Различия в реализации
этих процедур определяются различиями в организации таблицы
символических обозначений.
Из атрибутов, имеющихся в элементах таблицы символических
обозначений, нас будут интересовать только три следующих вида компонентов:
• type, который указывает тип из множества наперед определенных типов
и типов пользователя как одно из перенумерованных значений;
• size, который указывает размер массива dimn, определяющий
количество координат массива;
• address, который указывает адрес размещения начала массива. Мы
выбрали простое представление и можем сосредоточить внимание на других
проблемах. Если р указатель элемента таблицы символов, то, чтобы
получить атрибуты, достаточно написать p.type, p.size и т.д. Обработка
переменных с индексами рассматривается как дополнительная
специальная индексная операция, которая вычисляет итоговый индекс как
композицию частичных индексов и размерностей массива. Похожим
образом обрабатываются функциональные и процедурные выражения.
Если основа х анализируемой сентенциальной формы должна быть
приведена с помощью правила U::= xU, то для работы с семантикой
символических обозначений используется программа, которая:
1) заносит информацию в таблицу символов либо проверяет ее;
2) проверяет семантическую информацию, связанную с х;
222 Глава 9 Обработка управляющих данных информационных систем
3) генерирует тетрады;
4) связывает семантическую информацию с нетерминалом U.
Для окончательного определения тетрад необходимо определить их
элементы для сложных конструкций, включая проверки условий и организации
циклов. Семантическая обработка условных инструкций с одной стороны
довольно простая, а с другой - иллюстрирует некоторые из общих проблем
обработки составных конструкций. Обычно условная инструкция определяется
следующим образом:
условная инструкция ::= условие else инструкция! \ условие инструкция!
условие ::= if выражение then,
где выражение должно иметь тип boolean или logical в языках Algol либо
Fortran, перенумерованный тип либо любой численный тип в языке С. Исходя
из предположения, что ноль означает false, а не ноль - true и что значение
вычисленного выражения сохраняется в стеке до определения последовательности
выполнения команд, генерируется такая последовательность тетрад:
тетрады вычисления условий
p)Jzq+l,stk[0],0
тетрады обработки инструкции!
q) Jmp r
q+1) тетрады обработки инструкции!
г)...
Здесь Jz - тетрада перехода по нулевому результату генерируется
вследствие обработки правила условие ::= if выражение then.
Jmp - тетрада безусловного перехода, которая генерируется как
следствие обработки ключевого слова else. Поскольку во время предварительной
обработки и генерации кодов неизвестно, куда будет переход, мы фиксируем
номер тетрады как семантическую информацию метасимвола условие, чтобы во
время выполнения программы воспользоваться этой информацией. Заметим,
что каким бы сложным не было выражение, код для него уже сгенерирован.
Для реализации циклических и сильно ветвящихся фрагментов
программ необходимо ввести специальные дополнительные типы тетрад или триа-
д. желательно соблюдая базовый формат, выбранный на первичном этапе
построения виртуальной машины. При этом в каждом элементе определяется
условие или средства обработки элемента конструкции. Количество операций
ВМ в таком случае обычно не превосходит числа ключевых слов и операций
языка, включая все виды скобок. Метод программной реализации ВМ
создаваемого языка программирования широко использовался при создании первых
компиляторов языков типа Pascal, а команды такой ВМ, получили название Р-
кода. Уровень эффективности программ на Р-коде обычно далек от
предельного, но скорость выполнения программ достаточна для многих применений.
Отметим, что в реализации некоторых языков программирования
необходим контроль типа условия. Это хорошо реализуется обобщенной
программой контроля ожидаемого результата, которая вообще легко исполняется по
комбинированному алгоритму синтаксического разбора, рассмотренному в
конце предыдущей главы. Абсолютно похожим образом можно реализовать се-
Язык Ассемблера в программировании информационных и управляющих систем 223
мантическую обработку любой конструкции практически любого языка
программирования.
После этих преобразований семантические программы интерпретатора
обращаются к соответствующим процедурам либо машинным командам,
которые занесены в семантическую библиотеку для выполнения нужных действий.
Если во время выполнения программы в режиме интерпретации
имеются нужные данные для выполнения следующей операции, то сначала следует
проверить допустимость типов, выровнять типы по семантическим аксиомам
языка и лишь потом выполнить нужную операцию языка.
Таким образом, для семантической обработки на уровне интерпретации
семантических элементов необходимо знать базовые аксиомы
трансформирования данных разных типов и выполнения таких специфических операций, как
целочисленные умножения и деления. При разных типах совместимые данные
превращаются к наиболее мощному или общему из использованных форматов.
Если один из операндов имеет формат с плавающей точкой значит любой
другой целочисленный формат также трансформируется к формату с плавающей
точкой. Так, в вычислительных комплексах на базе процессоров ix86/87 данные с
плавающей точкой обыкновенно превращаются во внутреннюю форму в наиболее
мощном внутреннем формате.
Знаковые целочисленные данные расширяются путем знакового
расширения - распространения знакового бита на старшие разряды, а беззнаковые
путем добавления ведущих нулей к базовому формату. Сокращения форматов
обычно выполняются с помощью специальных функций, которые по
спецификации могут привести к утрате наиболее существенной информации. Еще один
интересный момент заключается в спецификации целочисленных умножений и
делений. Для предотвращения потерь информации при умножениях и делениях
нужно специфицировать результат умножения как данные двойного формата,
но это делается не во всех реализациях языков программирования, либо не для
всех типов данных. При делении семантические программы должны
предотвращать возникновение переполнений или обеспечивать обработку прерываний,
возникающих при переполнениях или делении на 0.
Из изложенного вытекает, что интерпретация может выполняться как при
непосредственной обработке программ, так и при работе по программе ВМ во
внутренней форме, но для этого необходимо сгенерировать внутренние коды ВМ.
9.4.3. Виртуальные машины синтаксического анализа
и логического вывода
Синтаксический анализ можно считать разновидностью логического
вывода в форме доказательства правильности программ, поэтому не удивительно,
что они имеют много общего с учетом разновидностей. Главной их
особенностью являются усложненная, по сравнению с машинами фон-Неймана,
регистрация состояния, при которой кроме указателя инструкций IP машины фон-
Неймана используется один (или несколько) групповых указателей предисто-
рии, занесенных в сетки. В частности это могут быть предистории вложенности
синтаксических конструкций, накопленные в стеке ВМ и/или предистория логи-
224 Глава 9 Обработка управляющих данных информационных систем
ческого вывода, необходимая для получения альтернативных результатов,
накопленная вместе с восстанавливаемыми данными в следе логического вывода
языка Prolog.
Разница между этими методами решения задач состоит, прежде всего, в
жесткой детерминированности доказательств синтаксической правильности и
многовариантной гибкости при логическом выводе по правилам логики
предикатов. Появление дополнительной степени свободы в машинах логического
вывода естественно ведет к увеличению емкости средств регистрации состояния
средствами фиксации следа трассы.
Такое разнообразие средств фиксации состояний порождает
возможность использования ВМ не только в режиме логического доказательства, но и
в режиме генерации вариантов решения задачи по тем же управляющим
конструкциям или их синтаксическим деревьям, который совершенно невозможен в
машине фон-Неймана. Таким образом, на общей основе могут быть получены
средства трансляции метаязыка и реконструкции синтаксических деревьев в ме-
таописания, а также средства подсказки допустимых вариантов текущих
синтаксических конструкций.
ВМ этого уровня содержат разнообразные операции управления
данными, структурами данных и адресов возврата, но используют фактически только
одну обобщенную операцию сравнения. Чтобы пользоваться такими
достаточно сложными ВМ на этапе проектирования языков программирования
целесообразно построить систему программирования на основе языка типа
Ассемблера для ВМ. Рассмотрим на примере команды ожидания (expectation)
построение макроопределения для некоторой ВМ синтаксического анализа в которой
семантическая обработка однозначно определяется ожидаемой операцией.
EXPC MACRO P,OL,DL,STCK,ALT,NST,METIM
IFB <STCK>
STT=16 ; Отсутствие манипуляций со стеком
ELSE
STT=48 ; Код записи в стек
ENDIF
IFIDN <STCK>,<RET>
STT^O ; Код выхода из вложенных конструкций
ENDIF
IFNB <ALT>
STT=STT+1 ; Признак альтернативной ветви
ENDIF
FL=4
IFNB <NST>
FL=FL+2 ; Усновка признака явного адреса продолжения
ENDIF
IFNB <METIM>
FL=FL+1 ; Установка признака поясняющего метаобраза
ENDIF
DW (P+FL)*256+4+STT,DL+OL*256 ; Формирование кода
; операции
Язык Ассемблера в программировании информационных и управляющих систем 22о
IFNB <ALT>
DW ALT-TREE ; Адрес альтернативной ветви
ENDIF
IF STT GE 48
DW STCK-TREE ; Адрес вложенного дерева
ENDIF
IFNB <NST>
DW NST-TREE ; Адрес управления продолжением анализа
ENDIF
IFNB <METIM>
DW METIM-TREE ; Адрес поясняющего метаобраза
ENDIF
ENDM
Макровызов для такого макроопределения может иметь до семи
операндов, которые определяют либо адресные ссылки для обобщенной ВМ
синтаксического разбора, либо ссылки для построения диалоговой метасистемы.
Команду ЕХРС можно рассматривать как универсальную для такой ВМ, так как она
обладает всеми возможностями для построения синтаксического дерева.
Обобщенный алгоритм интерпретации этой команды включает сопоставление типов
текущей пары лексем операнд операция, полученных от лексического
анализатора, с третьим и вторым аргументом макрокоманды. При несоответствии
текущей пары типу управление дальнейшим передается на альтернативную ветвь,
заданную пятым аргументом, а при ее отсутствии выполняется обработка
синтаксической ошибки.
Первый аргумент задает приоритет ожидаемой операции, который
позволяет автоматически выбрать режим выполнения операции: то ли ее
интерпретацию, то ли накоплении в стеке. Функция интерпретации однозначно
определяется вторым аргументом Продолжение обработки при успешном
выполнении определяется шестым аргументом, а при его отсутствии - следующим в
памяти элементом синтаксического дерева. Седьмой аргумент носит
вспомогательный характер при отображении метасимволов дерева, связанных с
операндом, заданным в третьем аргументе. Четвертый операнд управляет
дополнительными манипуляциями со стеком.
Для описания подобного языка достаточно пользоваться этой
единственной командой до полного отображения синтаксиса языка в синтаксическом
дереве. Для реализации компилятора или интерпретатора любого языка
достаточно иметь кроме описания языка на Ассемблере ВМ реализованный
интерпретатор ВМ синтаксического анализа и семантической обработки. Для
создания метасредств и средств подсказки необходимо разработать специальные
программы отображения, трансляции и коррекции метаописаний.
9.4.4. Виртуальные машины генерации объектных кодов
и оптимизации программ
В процессе лексического и синтаксического анализа для каждого
операнда определяется указатель на элемент таблицы символических обозначений
8 В.И. Пустоваров
Глава 9 Обработка управляющих данных информационных систем
программы, которые содержат описание всех переменных, констант и т.д. Здесь
преимущественно используются результаты восходящего разбора и нередко
реорганизуется синтаксис в соответствии с семантикой языка. Многие из
описанных здесь случаев перевода в тетрады могут быть трансформированы для
перевода в другие внутренние формы и для перевода в машинный язык целевой
ЭВМ. В ВМ этого уровня основное внимание уделено автоматизированному
анализу ресурсов и управлению эквивалентными преобразованиями.
Исторически различались три формы объектного кода:
• абсолютные команды, расположенные в фиксированных ячейках памяти
(после окончания компиляции такая программа немедленно выполняет-
ся);
• программа на языке Ассемблера (ее придется потом еще раз транслировать);
• программа на языке машины, представленная образами кодов и
записанная во внешней памяти в виде двоичного объектного модуля (такая
программа перед выполнением должна быть объединена с другими
подпрограммами и потом загружена для выполнения).
Главный недостаток первого варианта в том, что нельзя заранее и
независимо протранслировать несколько подпрограмм и потом объединить их
вместе для исполнения; все подпрограммы должны транслироваться
одновременно. Проще всего получить объектную программу на языке Ассемблера. В этом
случае не приходится формировать команды как последовательность битов, их
можно формировать как сложно связанные записи. Более того, можно
генерировать макровызовы, а соответствующие макроопределения заранее написать
на макроассемблере. Вместо генерации некоторых команд компилятор может
генерировать одну макрокоманду, а макроассемблер позднее сформирует
макрорасширения. Это позволяет также уменьшить объем компилятора. Не
обращая внимание на очевидные преимущества, трансляция через Ассемблер
обычно считается наихудшим из вариантов. И в самом деле, к процессу трансляции
добавляется еще один шаг, и этот дополнительный шаг часто требует столько же
времени, сколько продолжается, собственно, компиляция!
Большинство коммерческих компиляторов формируют объектную
программу в виде объектного модуля, то есть как последовательность записей,
которые содержат команды на машинном языке, информацию о возможности
перемещения и связывания кодов.
Семантические программы для конструкций процедурно- и
функционально-ориентированных языков, которые были предварительно рассмотрены
для нужд интерпретации и без снижения общности могут быть распространены
и на случай генерации кодов целевого процессора, либо ВМ реализуемого
языка. Однако для формализованного анализа в задаче необходимо построить
формальную модель ресурсов целевого компьютера либо ВМ. В ее состав
должны входить:
• модель информационных или запоминающих ресурсов;
• модель операционных ресурсов эквивалентных операциям языка
программирования, в виде таблицы команд объектной ВМ, которая вклю-
Язык Ассемблера в программировании информационных и управляющих систем 227
чает как ключи коды операции и базовых модификаций адресов, а как
характеристики поиска соответствующие машинные коды.
Поскольку схема распределения памяти и форматы областей данных
готовой программы определяются управляющими программами ОС, обращение
к ним сравнительно просто формируется во время компиляции. Очертим
общую схему распределения памяти и объясним, когда такое распределение
памяти могло бы быть полезно. Большинство процессоров имеют несколько
быстрых регистров, которые могут использоваться для сохранения промежуточных
результатов. Хотя задачу эффективного использования этих регистров можно
рассматривать как задачу распределения памяти, но мы все-таки рассмотрим ее в
связи с оптимизацией программ.
Мы исходим из того, что для каждой процедуры, которая
компилируется, компилятор отводит одну область данных. В эту область входят все
внутренние переменные, фактические параметры, переменные, определяемые
программистом, и временные переменные, используемые процедурами. В общем
случае компилятор имеет таблицу tbd, где перечислены все области данных
объектной программы. Элемент таблицы tbd для каждой области данных
содержит поле, в котором зафиксирован признак, статической либо
динамической.
Программа, осуществляющая присваивание изменяемых адресов, имеет
дело с одной процедурой. Ее nqraaa задача - отмечать в начале области место для
связи с предшественниками по трассе вызова, если оно используется, и для
формальных параметров. В таблице tbd фиксируется относительный адрес в сегменте,
который используется стандартными объектами фиксированного размера. Другая
задача в том, чтобы обработать элементы таблицы символов, соответствующие
формальным параметрам, а также переменным, описанным в этой процедуре, и
присвоить им адреса. Для каждого элемента таблицы символов делается следующее.
1. Элементу таблицы символов присваивается смещение, равное
значению текущего предела выделенного памяти, то есть, ему присваивается адрес
первой свободной ячейки или области данных.
2. Текущая таблица увеличивается на длину элементов, отводимых под
формальные параметры.
Это простая и очевидная обработка, и нет необходимости давать
добавочные объяснения. Требуется, чтобы для таблицы символов можно было
узнать размер области памяти, нужный для каждой переменной. В языках с
нестрогими условиями декларации данных эта информация неизвестна к
окончанию просмотра всей исходной (входной) программы. Поэтому для
присваивания адресов после обычного анализа необходим второй просмотр. Во время
первого главного просмотра компилятор генерирует команды машины
непосредственно из входного языка. Ссылки на переменные в командах машины
определяются указателями на соответствующие им элементы таблицы символов.
Во время второго, меньшего просмотра переменным присваиваются адреса,
которые запоминаются в элементах таблицы символов. Потом пересматриваются
команды машины и каждая ссылка на таблицу символов заменяется
соответствующим значением адреса, взятым из элемента, на который ссылается команда.
228 Глава 9 Обработка управляющих данных информационных систем
В языках, которые требуют описания переменных до их использования,
распределение памяти выполняется семантическими программами,
предназначенными для обработки деклараций данных. В этом случае возможен
действительно однопросмотровый компилятор.
Перед генерацией кодов тетрады расположены в том порядке, в котором
должны выполняться операции. Поэтому мы пересматриваем их
последовательно одну за другой для каждой генерируемой команды. Чтобы удалить
ненужные команды пересылок, нам придется постоянно следить за занятостью
аккумулятора, где выполняются все арифметические действия. Чтобы избежать
такого анализа мы выделили специальный указатель на значение
аккумулятора. Процесс генерации кодов отдельных команд опирается на каноническое
множество адресов и модификаций команд. Во множество адресов включаются
как минимум:
• прямая адресация глобальных и динамических данных;
• относительная адресация аргументов и локальных данных процедур и
функций в стеке;
• индексированная адресация глобальных и локальных данных.
На основе допустимых операций и форматов адресов данных строится
таблица tba, в которой аргументами являются последовательность команд и
формы адресации, сопоставимые с выборками из тетрадной формы
представления команд и характеризующихся простыми правилами формирования адресов
в объектном коде.
При изучении форматов объектных модулей особый интерес
представляют такие вопросы:
1. Как указываются, внешние подпрограммы и данные, требуемые
объектному модулю и каким образом определяют "точки входа" к командам либо
данным в модуле, которые могут использоваться другими подпрограммами?
2. Как достигается перемещаемость объектной программы, то есть
возможность размещать ее в любом месте оперативной памяти?
Объектный модуль, составляемый главным образом из двоичных
текстов - команд на машинном языке и данных, составляющих объектную
программу и переносимых в память без изменений. Модуль может содержать один
либо больше таких сегментов текста, называемых кодовыми сегментами.
Внутри сегмента кодов позиция каждого байта текста относительно начала
сегмента фиксирована, и этот порядок должен быть сохранен при загрузке сегментов
в память перед выполнением программ. Порядок расположения кодовых
сегментов в памяти может быть произвольным, и сегмент чаще всего можно
поместить в любое место памяти.
Таким образом, относительный адрес любой входной точки
относительно начала сегмента является константой и для определения такого имени для
компоновщика достаточно рядом с его символическим образом определить
сегмент и относительный адрес. Словарь внешних символов ESD (external symbol
dictionary) определяет и идентифицирует (дает единственное имя и номер)
таким объектам: кодовым сегментам объектной программы; внешним ссылкам на
Язык Ассемблера в программировании информационных и управляющих систем 229
программные сегмента и другие входные точки, к которым обращаются в этом
модуле, но которые не включены в него Они определяются для этого модуля
компоновщиком LINK; глобально достижимые точки входа - это фактически
ячейки в середине данного модуля, на которые можно ссылаться из других
модулей, а внешние имена типа external определяют коды программ либо данные,
определенные в других модулях. Объединение логических сегментов данных в
большие физические сегменты требует возможности коррекции относительного
адреса в уже сформированной команде на значение относительного смещения
начала логического сегмента.
Обычно сегмент кодов объединяет:
• команды для главной программы;
• команды для процедур и функций;
• константы программы.
В состав модуля включают и сегмент определения глобальных данных
главной программы либо подпрограмм. Однако нет необходимости строго
придерживаться этих категорий; сегмент с любым текстом можно, если это
удобно, включить в сегмент кодов.
Под оптимизацией программы имеется в виду обработка, связанная с
переупорядочиванием и изменением операций в компилируемой программе с
целью получения более эффективной объектной программы. Лучшие
программы, оптимизируемые компиляторами дают объектные коды для сложных
программ, которые по качеству не уступают программам, написанным на языке
Ассемблера программистами высокой квалификации, но со значительно
меньшими затратами.
Критерии оптимизации могут быть упорядочены по составу. Наиболее
распространены минимизирующие критерии оптимизации:
• минимум затрат памяти центрального процессора;
• минимум затрат времени центрального процессора.
Нужно учесть еще и то, что ряд методов уменьшает затраты в обоих
этих направлениях. Однако в системах, которые имеют дело с фиксированными
и ограниченными блоками памяти, кроме оптимизируемых составляющих,
сейчас обращают внимание и на ограничительные составляющие критериев.
Кроме того, в наше время все больше обращают внимание на такие
дополнительные составляющие критериев:
• получение требуемой точности результатов;
• получение нужной надежности работы информационной или
управляющей системы.
Различают две категории преобразований, которые оптимизируют
программы:
1) преобразования, осуществляемые на уровне объектной программы и
полностью зависят от объектного языка;
2) преобразования входной программы в ее внутренней форме, которые
не зависят от объектного языка.
230 Глава 9 Обработка управляющих данных информационных систем
Методы первой категории должны обеспечить эффективное
использование ресурсов целевой ЭВМ на этапе компиляции и могут использоваться для
реализации языков программирования и т.д. Здесь используются три основных
типа методов:
1) эффективное использование запоминающих ресурсов целевого
процессора;
2) эффективное использование операционных ресурсов процессора либо
системы команд;
3) эффективное кодирование данных, их структур и комплексов,
особенно в слабо формализованных областях.
Наиболее общий метод распределения запоминающих ресурсов состоит
в том, что все наличные запоминающие ресурсы общего назначения
разделяются на m категорий, например:
• регистровая или сверхоперативная память для каждого формата
данных, которая включает R регистров;
• оперативная память размером М, на которую проектируются данные
всех форматов;
• дисковая или остаточная виртуальная память размером V байтов.
Здесь простейшим является распределение ресурсов по числам нужных и
наличных элементов, выполняемое последовательным выделением ресурсов по
счетчику и по отметкам занятости-освобождения ресурсов. Более сложным
является подход с определением специализированных ресурсов, но он может
помочь в выполнении оптимизации методами другого типа. Так в процессорах
ix86/87, по крайней мере в реальном режиме, имеется довольно сложная
специализация регистров общего назначения на аккумуляторы, счетчики, базовые и
индексные регистры, с которыми могут оперировать лишь отдельные
специальные команды.
Для эффективного использования специализируемых операционных
ресурсов необходимо расширить таблицу tba и сделать более гибким и
многоэтапным алгоритм поиска в этой таблице. Он должен начинаться с проверки
возможности применения наиболее мощных групповых ресурсов, таких как
команды перехода по счетчикам, и команды обработки цепочек байтов, слов и
др. и заканчиваться проверкой элементарных канонических ресурсов.
Неэффективность многих компиляторов, обусловленная именно слабой выходной
лексикой и связанной с ней невозможностью генерирования мощных групповых команд,
может быть устранена этим методом.
Автоматизированное эффективное кодирование типов данных
пользователя используется довольно редко и, как правило, является заботой
программиста при конструировании собственных типов данных, чаще всего
перенумерованных (типа enum в языке С). Здесь особую роль играет подбор таких кодов,
которые позволяли бы проверку отношений традиционными машинными
операциями, такими как арифметические отношения, отношения вхождения,
проверяемые по маскам, и комбинированные отношения. В СИИ эти задачи
затрудняются в связи с необходимостью построения отношений унификации с
Язык Ассемблера в программировании информационных и управляющих систем 231
разными преобразованиями системы координат (масштабными и поворотами).
Однако их использование нуждается в разработке специальных процессоров
нового типа.
Методы второй категории пригодные почти для любого
алгебраического языка - Pascal, С и т.д. Здесь используются четыре основные метода:
• свертка, то есть выполнение операций, операнды которых известны во
время компиляции;
• исключение лишних операций либо повторяемых вычислений (в
основном за счет единовременного одноразового программирования общих
подвыражений), включая циклические вычисления;
• замена сложных операций более простыми за счет эквивалентных
преобразований;
• эквивалентные преобразования графовой модели программы.
Степень оптимизации, которая выполняется компилятором зависит от
многих факторов. Не все компиляторы или не все их режимы должны
проводить максимально полную оптимизацию. Например, не нужно применять
сложные методы в компиляторах или их режимах, интенсивно используемых для
отладки, поскольку они сильно изменяют порядок операций, что осложняет
поиск ошибок в сформированнэй программе Поэтому мы не рассмотрим единую
общую схему полной оптимизации. Взамен этого мы остановимся на двух
простых методах, один из которых должен быть реализован в любом компиляторе.
Потом мы рассматриваем умеренную оптимизацию циклических программ,
которая реализуется довольно легко. Дальше мы рассматриваем более сложную
схему оптимизации, которая требует более полного анализа структуры
исходной программы.
Проще всего оптимизация выполняется в пределах линейных участков
или последовательностей команд, которые выполняются по порядку операций
с одним входом и одним выходом (первой и последней операции
соответственно). Например, последовательность операций, выполняющая присваивания
любого вида, создает линейный участок. В середине линейного участка обычно
проводится два вида оптимизации: свертка и устранение лишних операций.
Более сложной задачей является выполнение эквивалентных и адекватных
преобразований на линейных участках с целью упрощения аналитических
выражений при условии обеспечения достаточной точности и надежности решения
задач. До последнего времени такие преобразования были заботой аналитика
проблемной области, в которой решается задача, но в связи с быстрым
развитием методов средств искуссгвенного интеллекта вырисовываются
возможности использования встроенных автоматизированных методов аналитических
преобразований.
Основой механизма автоматических преобразований являются
соответствующие таблицы, в которых хранятся эквивалентные и адекватные
выражения во внутренней форме, которая используется в системе. Преобразования
обычно выполняются в две фазы: сначала выполняют преобразование к
некоторой базовой канонической форме и лишь потом к одной из
минимизированных форм. В отличие минимизации двоичных и логических функций, где Крите-
232 Глава 9 Обработка управляющих данных информационных систем
рием может быть количество термов или букв в выражении, в общем
алгебраическом случае необходимо учитывать еще и весовые коэффициенты отдельных
операций, учитывающие затраты машинных ресурсов.
9,4.5. Виртуальные машины информационного поиска
и распознавания образов
ВМ этого типа имеют особые представления операций и методы их
реализаций. Коротко выделим основные функции ВМ этого уровня:
• сопоставление одиночных образов, для которых характерен комплекс
предварительных действий:
♦ поиск ядра объекта;
♦ поворот для максимального совмещения эталонов;
♦ трансформация системы координат, включая изменения масштабов
по осям координат с учетом оптимальных путей сопоставления с
эталоном;
• обмен информацией с соседними уровнями интеллектуальной системы;
• сопоставление групповых образов, при котором используются шаблоны с
заданными функциональными и логическими связями между объектами.
Для построения таких ВМ важно определить единообразную для всех
уравнений и ветвей форму представления окончательных и промежуточных
решений.
Краткие итоги
В этой главе рассмотрены методы синтаксического и лексического
анализа, ориентированные на построение эффективных элементов системных
программ на языке Ассемблера. Их использование позволяет во много раз
повысить скорость выполнения системных программ.
ГЛАВА 10
УПРАВЛЕНИЕ РЕШЕНИЕМ ЗАДАЧ И
ОРГАНИЗАЦИЯ ВЫЧИСЛИТЕЛЬНЫХ ПРОЦЕССОВ
В -мкт /лаве рассмотрены принципиальные основы методов
управления вычислениями в системах с мноючис пенными
вычислите чьными процессами и особенности их реализации с
применением машинно-ориентированных средств и языка Ас се чС>
лера процессоров ix86
10.1. Организация многозадачности в системах
персонального и коллективного пользования
Наиболее очевидной причиной необходимости управления процессами
вычислений является необходимость решения нескольких задач в одной
вычислительной системе (ВС), называемое мультизадачностью, многозадачностью
или устаревшим термином - ''многопрограммность", под которой понимают
способность компьютера решать несколько задач одновременно. Систему
управления многозадачностью можно рассматривать как третий, помимо
обращений к подпрограммам и макросредствам, способ управления
подчиненными вычислениями. Однако для его рационального использования ресурсы ВС
должны быть разделены на части по структурным составляющим или
виртуально, то есть кажущимся способом, соответствующим методу решения задачи.
Наличие нескольких процессов в персональных системах чаще всего незаметно
для пользователей.
Четкое определение понятия "задача" отсутствует, а как его
эквиваленты применяются термины "пользователь", "задание" или "окно программы на
экране дисплея". В наиболее общем определении под задачей понимается
"последовательность взаимосвязанных действий, ведущих к достижению заданной
информационной цели". В более простом смысле под задачей понимается
программа, которая выполняется, или ожидает выполнения, пока выполняется
другая программа. Применительно к компьютерам в определение задачи
обычно включаются ресурсы, требуемые для достижения цели задачи: объем
памяти, время работы процессора, дисковое пространство и др. Процесс решения
задачи может быть рассмотрен как использование вычислительных ресурсов и
взаимодействие задачи с ними. Как будет показано далее, в процессорах ix86
под задачей понимается информационно-управляющий объект, обладающий
своим виртуальным адресным пространством и состоянием, отражающим в
текущий момент состояние архитектурных элементов процессора в форме
комплекса данных.
Многозадачность рассматривается с двух разных позиций:
234 Глава 10 Управление решением задач и организация вычислительных процессов
1) рациональное разбиение процесса решения задач на процессы,
протекающие в параллельных, автономных и относительно независимых устройствах;
2) обслуживание группы пользователей ресурсами мощной
вычислительной системы.
Если в первом случае проблемы информационных и управляющих
процессов должны быть решены на этапах проектирования и отладки программ в
форме статического планирования, то во втором случае дополнительно
необходимо решить задачу предотвращения случайных или преднамеренных
взаимодействий с информацией, предназначенной для других пользователей. Эти
задачи прямо не связаны между собой, что позволяет на начальном этапе
изучения рассматривать их отдельно.
Инициализация задач и вычислительных процессов основывается на
инициализирующих событиях в ВК. Начальное инициализирующее событие
для ВК на базе процессоров семейства х86 задается сигналом "Сброс" (Reset),
подаваемым на процессор и инициирующим его работу в реальном режиме со
стартового адреса находящегося в конце адресного пространства по
программам BIOS. При этом создается единственный вычислительный процесс (ВП),
который в дальнейшем может передавать управление произвольной задаче
пользователя. Наличие в ВК аппаратных средств, обрабатывающих
информацию автономно без непосредственного участия ЦП, приводит к возможности
одновременного выполнения ряда независимых, но связанных ВП. Их
взаимодействие основано на использовании аппаратных прерываний,
приостанавливающих работу по текущему процессу и переводящих ЦП на процесс
обработки прерываний. Наличие системы прерываний делает возможной
многозадачность, управляемую сигналами управления задачами, поступающими чаще
всего от таймера, наличием данных для решения задач и приоритетами отдельных
задач.
Задача, выполняемая в ЦП в текущий момент времени, называется
активной. Другие задачи при этом либо приостановлены до команды вводимой с
клаиатуры, либо находятся в состоянии ожидания требуемых данных или
требуемых ресурсов ВК. Управляющую программу, осуществляющую просмотр
комплекса задач, ожидающих обслуживания центральным процессором или
процессорами, для выбора к выполнению наиболее приоритетных задач
принято называть супервизором или гипервизором.
Минимальная информация о задаче должна включать:
• идентификационный номер или имя задачи;
• приоритетное число определяющее очередность выполнения задач и других
ВП;
• информацию необходимую для восстановления прерванной задачи или
запуска новой задачи.
Кроме этого в системах с динамическим распределением внешних
устройств необходимо фиксировать их текущее распределение и предотвращать
несанкционированное вмешательство посторонних задач в работу физических
и виртуальных устройств, непредназначенных для разделения (sharing) между
задачами.
Язык Ассемблера в программировании информационных и управляющих систем 235
Построим процедуру простейшей супервизорной программы, которая
может управлять переключением задач в реальном режиме процессоров ix86, с
фиксированным размещением программ в памяти и распределением устройств
ввода-вывода для задач. В этом случае нет необходимости в сохранении
состояния памяти приостанавливаемой задачи на диске, называемом сверткой
задачи, с необходимым последующим восстановлением в памяти, называемым
разверткой задачи. Если состояние регистров приостановленной задачи
фиксируется в строго определенном порядке, то для их восстановления достаточно
хранить в памяти полный указатель стека текущей задачи. Отсюда следует, что
каждая задача или другой контролируемый супервизором ВП должен иметь
свой индивидуальный стек. В то же время прерывание в принципе может
использовать для своей работы стек прерванной задачи, при этом, однако,
необходимо гарантировать достаточный объем стека, чтобы не выйти за его пределы.
Для таблицы задач со структурой элементов вида:
9
TbTskElem STRUC
Ready DB
Prty DB ?
StPtr DW ?
Iden DQ ?
TbTskElem ENDS
TbTskSeg SEGMENT
Структура элемента таблицы задач
Байт готовности задачи к продолжению
Приоритетное число
Полный указатель стека реального режима
8-байтная символическая идентификация
Конец сегментного блока
Начало сегмента таблицы
FreeTime DW 2 dup 0 ; Область для счетчика
; свободного времени
AD$ACT DW ? ; Смещение адреса элемента активной задачи
Tsk EQU $
RIPC <123> ; Количество элементов таблицы :3
Tsk&Nb TbTskElem <0,0,EStkPtr&Nb+4,Nb>; Элемент таблицы
ENDM
TbTskSeg ENDS ; Конец сегмента таблицы
Такой сегмент статически резервирует память для элементов таблицы
задач, но ОС в процессе своей инициализации и инициализации вновь
поступающих задач должна заполнять и корректировать таблицу системными
программами. Для ОС бортовых приборов и устройств с фиксированным составом
задач удобнее заполнить эту таблицу значениями, используя отдельные
инициализации структур для каждой задачи Но при начальной загрузке нужно
подготовить и инициализацию стека для каждой задачи.
Отметим, что для встроенных в приборы ОС реального времени (ОС РВ)
поле идентификации таблицы необязательно. Более того при реализации более
эффективных распределенных таблиц задач идентификация задачи может быть
легко восстановлена по ее номеру в списке элементов.
Программа супервизора в кодовом сегменте может представлена
следующими командами:
SvcCode SEGMENT
ATB$TSK DD Tsk
236 Глава 10 Управление решением задач и организация вычислительных процессов
ASSUME CS:SvcCode
SvcMonitr PROC FAR
SvcEntr:CLI ;
; Циклический монитор ядра
MONPR:PUSHA
PUSH DS
XOR DX,DX
LDS SI/ATB$TSK ;
LBM:
LEM
LMM1
LBT:
LODSW
TEST
JZ
CMP
JNZ
CMP
JA
LEA
XCHG
:LEA
JMP
:TEST
POP
POP
JNZ
IFZ
INC
JNZ
INC
JMP
AX, AX
LMM1
AL,255
LEM
DH,AH
LEM
DI,[SI-2]
AX,DX
SI,[SI+10]
LBM
DH,DH
DX
AX
RST$TSK
FnTsk
FreeTime
LBT
FreeTime+2
LBT
ENDIF
; Реинициализация задачи
RST$TSK:CMP DI,AD$ACT
JZ LRM
Запрет прерываний
Загрузка начального адреса
таблицы задач
Загрузка приоритета и состояния
очередной задачи
Контроль готовности
Переход, если задача не готова
Контроль приоритета
Переход, если приоритет
приостановленной задачи выше
Подготовка адреса указателя и
приоритета-состоянния
предпочитаемой задачи
Переход к следующему элементу
Контроль наличия готовых задач
Восстановление сегмента
Контроль отсутствия явной
фоновой задачи
Счет младших разрядов
Счет старших разрядов
Цикл фонового подсчета
резервов времени
Обход переключения задач, если
приостановленная задача
остается активной
Переключение задач
CLI ; Запрет прерываний при переключении стека
PUSH ES ; Сохранение сегментного регистра
PUSH FS ; Сохранение сегментного регистра [*3]
PUSH GS ; Сохранение сегментного регистра [*3]
SUB SP,108; Для возможности 32-битового режима
MOV BP,SP
FSAVE [ВР] ; Сохранение среды и регистров
Язык Ассемблера в программировании информационных и управляющих систем 237
сопроцессора
Загрузка адреса элемента
активной задачи
Сохранение стека
старой задачи
Сохранение сегмента стека
Фиксация адреса элемента
активной задачи
Восстановление стека
новой задачи
Восстановление
сегмента стека
MOV SI,AD$ACT
MOV StPtr[SI],SP
MOV StPtrfSI+2],SS
MOV AD$ACT,DI
MOV SP,StPtr[DI]
MOV SS,StPtr[DI+2]
MOV BP,SP
FRSTOR [BP] ; Восстановление среды и
регистров сопроцессора
ADD SP, 108 ; Для возможности 32-битового режима
POP GS ; Востановление сегментного регистра [*3]
POP FS ; Востановление сегментного регистра [*3]
POP ES ; Востановление сегментного регистра
*** Конец переключения
DS ; Восстановление DS
LRMrPOP
РОРА
STI
IRET
SvcMonitr ENDP
SvcCode ENDS
Эту процедуру можно довольно четко разделить на часть просмотра
состояния задач и часть переключения задач. При этом супервизор как бы не
является самостоятельной задачей, а использует такие ресурсы переключаемой
задачи, как стек и регистры. Большая часть этой программы должна использоваться в
режиме запрета прерываний, что бы не вызвать разрушений стеков задач.
В этой процедуре использованы следующие допущения:
• значение приоритетного числа возрастает с ростом приоритета (в
большинстве современных ОС приоритетное число убывает с ростом
приоритета);
• готовность задачи к продолжению или требование ресурса времени ЦП
определяется наличием всех единиц в байте готовности (хотя проверка
по нулям может быть выполнена быстрее).
В примере приведена встроенная фоновая (самая низкоприоритетная)
задача в форме подсчета свободного времени процессора, оформленная в
неявном виде. Вместо нее может быть записана команда HLT, которая переводит
процессор в такое состояние, в котором приостанавливается выполнение
команд Ее единственное преимущество состоит в том, что процессор не занимает
2оО Глава 10 Управление решением задач и организация вычислительных процессов
шину данных компьютера и не препятствует ее взаимодействию с каналами
прямого доступа и другими процессорами системы.
Управляющие программы ОС подразделяют на резидентные,
находящиеся в главной памяти ВК при ее функционировании, и транзитные, при
необходимости копируемые в главную память из внешних накопителей. Ядром ОС
обычно называют комплекс резидентных программ, в который всегда входят
супервизор и обработчики прерываний и чаще всего полный комплект
программ ввода-вывода.
Привести в действие подобную программу супервизора можно только с
использованием вызывающей последовательности, обращающейся к
программному прерыванию. Кроме того, если известен адрес входной точки
системной программы, то к нему можно обращаться с помощью команд
PUSHF ; Сохранение флагов
CALL FAR PTR MONPR ; Обращение по прямому адресу
Вызывающие последовательности удобно задавать в виде
макровызовов. Приведем примеры ряда макроопределений, используемых для подобных
целей.
InitTsk MACRO NT,PRT,NM
LDS SI,ATB$TSK
Макроопределение инициации
задачи
Загрузка начального адреса стека
LEA SI,NT*12[SI] ; Загрузка адреса входа
IFNB <NM>
InitStk NT,NM ; Макровызов первичной
; инициации стека
ENDIF
MOV WORD PTR Ready[SI],PRT*256+0FFH
ENDM
Это макроопределение обеспечивает изменение приоритета
существующей задачи и подготовку к выполнению новой задачи, если задан третий
аргумент.
SendSignTsk MACRO NT,MSK
LDS SI,ATB$TSK
LEA SI,NT*12[SI]
OR Ready[SI],MSK
PUSHF
CALL MONPR
ENDM
WaitSignTsk MACRO NT,MSK
LDS SI,ATB$TSK
LEA SI,NT*12[SI]
маска канала
Загрузка начального адреса стека
Загрузка адреса входа
Установка бита сигнала
Обращение к супервизору
маска канала
Загрузка начального адреса стека
Загрузка адреса входа
AND Ready[SI],PW-MSK ; Сброс бита сигнала
PUSHF
CALL MONPR ; Обращение к ядру
Язык Ассемблера в программировании информационных и управляющих систем 239
ENDM
Для работы с такими макроопределениями в прикладной программе
нужно иметь информацию о входной точке программы супервизора, о
начальном адресе таблицы задач и расположении блоков инициирующей
информации. Поэтому практически во всех ОС и их расширениях имеются функции,
обеспечивающие возврат программе пользователя подобной адресной и
управляющей информации. Чтобы уменьшить количество такой информации
стараются использовать минимум входных точек прерываний с выбором требуемой
системной функции и подфункции по содержимому регистра АХ.
Для обращения к подпрограммам ядра и структурам данных,
встроенным в ОС используют функциональные расширения базового языка
программирования двух уровней: внутрисистемного, для эффективной работы
системных и специальных программах, и уровня пользователя, определенного для
решения проблемных задач и организации их взаимодействия с системными
процессами источниками и потребителями данных.
Эти функции охватывают средства манипуляций со встроенными
структурами данных, такими как очередь, таблицы задач и таблицы ресурсов, а
также синхронизацию ВП пользователя с внутренними ВП обработки
прерываний. В базовом варианте (ОС РВ) irmx [1] все объекты создаются динамически,
в то время, как динамическое создание объектов полезно лишь при недостатке
памяти для сохранения всех объектов и в большинстве случаев приводит к
потере эффективности программного воплощения ОС РВ. Поэтому кроме
динамических определений объектов ОС в средства уровня генерации ОС
целесообразно включить процедуры статического создания объектов. В отличие от
функций динамического создания их имена будем записывать заглавными
буквами сразу после спецификации базовых данных. В специализированных ВК с
программно управляемой системой ввода и сбора информации использование
файловой системы, традиционно ориентированной на однопотоковую
обработку последовательных данных, практически бессмысленно. Поэтому во
многих специализированных ОС РВ файловая система исключается, и
информационный обмен реализуется на основе непосредственного использования системы
прерываний.
Синхронизация процессов ОС обычно обеспечивается
функциональными вызовами синхронизирующих примитивов (см. главу 6). Средства для
работы с примитивами разрабатываются в рамках библиотек специальных функций
их обслуживания, использующих как параметр имя, численный идентификатор
или указатель примитива, заданный при определении объектов. Рассмотрим
для примера реализацию очередей, которые могут включать в себя внутренние
элементы системных объектов более высокого уровня, например, сообщения
Основные функции обслуживания очередей, представленные в виде
заголовков на языке С:
createqueu (char el$lnth, int id$queue, int nb$el) - динамическое создание
очереди, используемое в динамических специализированных ОС;
Createq (...)- статическое создание очереди;
writeq (str*el, int id$queue) - запись элемента в очередь;
240 Глава 10 Управление решением задач и организация вычислительных процессов
readq (str*el, int id$queue) - чтение элемента из очереди.
Здесь очереди с разными приоритетами объектов могут быть
организованы как совокупность отдельных очередей для объектов каждого приоритета.
Главным объектом специализированной ОС является последовательность
запросов управляющих воздействий на внешние устройства. Из очереди запросов
на воздействия необходимо выдавать сигналы на исполнительные устройства
системы управления.
Основу организации переключения задач обеспечивает супервизор РВ,
который определяет наиболее приоритетную из готовых задач и передает
управление этой задаче. Обращения к супервизору РВ выполняются в конце
выполнения системных функций, которые могут изменить состояние задачи. К
таким функциям относят:
• функции ожидания, которые переводят текущую активную задачу в
режим ожидания готовности;
• функции передачи, которые изменяют состояние процесса-приемника
информации, либо управляющего сигнала.
Они предназначены для эффективной организации переключения задач
путем изменения кода состояния задачи в таблице задач.
В специализированных ОС РВ состояние задачи определяется байтом
состояния, в котором наличие единиц по всем восьми каналам готовности
означает готовность задачи. Каждый с из 8 каналов готовности используется для
фиксации состояния готовности по одному либо нескольким информационным
каналам системы ВП. При этом лучше использовать систему индивидуальных
каналов для объектов ОС. Включим в состав функций специализированной ОС
РВ процедуру установки готовности задаче по определенному каналу.
Обобщенные функции обращения к ядру ОС могут быть
сформулированы так:
extern void set$task$ch (char nb$task, char nb$ch);
Эта функция устанавливает готовность задачи с номером nb$task по
каналу nb$ch, передает разрешающий сигнал z-ой задаче поу-му каналу;
extern void reset$task$ch (char nb$task, char nb$ch);
Эта функция снимает готовность задачи с номером nbStask по каналу
nb$ch передает запрещающий сигнал z-ой задаче поу-му каналу.
Приоритеты задач - фиксированные и их изменение в динамике работы
ОС обычно не предусматриваются.
Выполнение этих функций заканчивается обращением к супервизору в
режиме запрета прерываний. Начальная часть этих функций может быть
реализована как конъюнкция или дизъюнкция в режиме inline с последующим
обращением к супервизорной программе. Функции специализированной ОС
более высокого уровня, изменяющие состояние задач должны обращаться к этим
функциям в конце своего выполнения.
Язык Ассемблера в программировании информационных и управляющих систем 241
Подобного рода переключения задач реализуются во всех классах
многопрограммных ОС, основными из которых являются:
• многозадачные ОС. управляемые приоритетами;
• ОС с разделением времени, в которых всем пользователям стараются
предоставить равные права,
• ОС виртуальных машин, которые позволяют эксплуатировать ПО
разных компьютеров с разными ОС.
Для информационных систем пригодны любые ОС. организующие режим
коллективного доступа к данным и возможность эффективного
функционирования компьютера в сети. Сетевые ОС могут быть построены также на основе
супервизоров любой из перечисленных ОС. Среди управляющих систем особо
выделяются системы управления техническими и технологическими процессами, для
которых характерна необходимость соблюдения жестких временных
соотношении. К таким управляющим системам относятся системы на базе встроенных
приборных компьютеров, применяемых в такик отраслях как авиация, системы
коммутации и связи, управление непрерывными технологическими процессами в
металлургии, нефтепереработке, управление станками с числовым программным
управлением и сложными робототехнпческими комплексами.
10.2. Особенности реализации управления задачами
в системах реального времени
Для построения ОС реального времени (РВ) в современном
программировании используются такие базовые программные средства как языки РВ, ОС
РВ общею назначения, специализированные и минимизированные ОС. Такие
языки РВ как Ada и Modula-2 включают разные средства и возможности
построения полных специализированных ОС РВ. Но недостаточно высокая
эффективность реализации примитивов взаимодействия ВП и программ
обработки прерываний, строящихся по стандартным схемам сужает сферу
использования таких языков
Современные версии процедурно-ориентированных языков
традиционного типа Pascal и С также имеют возможности построения системных
программ с использованием возможностей встроенного или прикомпонованного
языка Ассемблера, который при большом опыте программиста позволяет
получать наиболее эффективные программы. Учитывая ориентацию на
использование стандартных протоколов сетей, спецификации которых оформляют на
языке С, чаще всею именно его выбирают как базовый язык программирования
ОС РВ в сочетании с языком Ассемблера базового процессора системы РВ.
Такие ОС РВ, как irmx/86 и ее последующие модификации позволяют
строить любые конфигурации ОС РВ, однако наличие в реализации версии ОС
РВ системных вызовов нескольких уровней делает их практически
бессильными в решении задач со скоростью нужной для систем РВ. Специализированные
ОС чаще всего возникают при генерации подмножества ОС типа irmx,
адаптации зарубежных ОС и предыдущих отечественных версий к новым условиям
9 В. И Пустоваров
242 Глава 10 Управление решением задач и организация вычислительных процессов
использования. Такая работа целесообразна при разработке систем РВ
минимально отличающихся от предшественников.
Традиции фирмы Intel привели к построению на базе
объектно-ориентированного подхода семейства реконфигурируемых ОС РВ irmx с последующей
разработкой средств ее расширения для других процессоров этой и других
фирм. В рассмотренном подходе определяются стандартные объекты ОС РВ,
на которых мы уже останавливались при изучении средств программирования
ввода-вывода и для которых разработаны библиотеки процедур (методы)
взаимодействия этих объектов. В качестве основных объектов в irmx выбраны:
• управляющие - семафоры, сигналы прерываний, задачи, приоритеты
задач и задания;
• информационно-управляющие - файлы и сообщения, которые
передаются от источников в почтовых ящиках.
• информационные - динамически порождаемые сегменты данных.
Внутренняя реализация этих объектов скрыта от пользователя и делает
недоступными для использования таких полезных объектов для построения
специализированных ОС, как очереди, используемые при передаче сообщений
и в работе с сетевой файловой системой.
Такой вариант объектного подхода используем как базу для изложения
материала по построению ОС, учитывая цель разработки специализированных
ОС нужно исключить основные недостатки универсальной системы типа irmx:
• избыточность динамических структур и динамическая инициализация
системы, порождаемая средствами конфигурации;
• многоуровневая система вложенных процедур и программных прерываний;
• избыточный для большинства процедур компактного варианта ОС
параметр контроля завершения;
• реализация даже простейших действий, выполняемых одной-двумя
машинными командами через систему вложенных процедур.
Возможность быстрого построения новой версии ОС РВ, или ее
адаптации к модифицированной структуре системы РВ обеспечивается построением
библиотек, расширяющих базовый язык программирования. Таким образом, за
основу построения ОС РВ с разнообразными структурами управляющих
данных, в том числе и многопроцессорными, целесообразно модифицировать
базовую компактную ОС РВ средствами быстрого решения задач, критичных ко
времени получения результатов.
10.2.1. Требования к ОС реального времени
Требования определяются прагматикой систем управления, составом и
структурой управляющей аппаратуры и управляющего ВК. Технические
требования к взаимодействию ПО с аппаратурой могут быть сформулированы так:
• обеспечение надежной и быстрой обработки прерываний от таймера или
другого устройства, которое задает цикл работы исполнительной
аппаратуры системы управления;
Язык Ассемблера в программировании информационных и управляющих систем 243
• обеспечение достаточного времени на выполнение проблемных задач
системы управления, которые определяют циклы управления и
проблемные вычисления.
Для разрабатываемых оригинальных систем управления на базе
многопроцессорных ВК целесообразно расширение базовых ОС РВ средствами
контроля коррекции и организации повторных вычислений при обнаружении
ошибок или сбоев в аппаратуре.
Будем различать общие требования к ОС реального времени (РВ) и
требования для специализированных ОС РВ. К общим организационным
требованиям относятся:
• управление процессом вычислений в соответствии с приоритетами
задач;
• согласование взаимодействия программ обмена и организации ВП в
многопроцессорной структуре системы РВ:
• обобщение средств обработки прерываний и их организация
взаимодействия с другими ВП;
• взаимодействие с сетевыми средствами для синхронизации ВП,
получения справочной информации и решения задач планирования
вычислений;
• обслуживание аппаратно-программных средств повышения надежности
системы, исключения аварийных ситуаций и некорректного управления;
• соблюдение ограничений по времени для задач с жестким ограничением
времени;
• использование форматированных запросов для передачи из программ
управления, к задачам выдачи управляющих воздействий.
ОС РВ должна обеспечивать следующие специальные режимы
использования ПО:
• динамическое пособытийное управление в РВ с жесткими временными
ограничениями для промышленных технологических задач;
• связь с отдаленными рабочими станциями информационных сетей и
ЭВМ других уровней;
• режим технологической подготовки исполняемых программ;
• режим настройки и контроля программ обработки для объекта
управления на обработку следующего запроса с помощью
меню-ориентированного и графического интерфейса
• Особые требования к ОС РВ управляющих систем:
• оперативное взаимодействие (обмен) с блоком управления на базе
обратной связи по результатам управления;
• поддержка стратегии управления с минимально возможным простоем
основного оборудования системы управления;
• реконфигурацию структуры и перераспределение задач между
процессорами.
Последние из этих требований обычно реализуются в проблемных
программах систем управления, но в связи с развитием ПО целесообразно рассмот-
9*
244 Глава 10 Управление решением задач и организация вычислительных процессов
реть вопрос об их включении в структуру ОС РВ на правах вызываемых
программ в обработчики прерываний.
10.2.2. Обобщенная структура специализированной
ОС реального времени
Основу воплощения системы РВ составляет ОС РВ или
специализированные управляющие программы, объединенные в специализированную ОС.
Использование таких универсальных ОС РВ как irmx/86 в полном объеме для
случая различных конфигураций систем управления как на основе
многопроцессорной системы, так и в форме автоматизированного рабочего места (АРМ)
с контроллерами чувствительных элементов и исполнительных устройств
нецелесообразно. Причиной является иерархическая избыточности,
обеспечивающей возможность подключения средств отладки ВП. Каждая
специализированная ОС проектируется из анализа наличия свободной памяти и полного объема
памяти, необходимого для выполнения оперативных задач.
В особом внимании нуждаются так называемые задачи РВ, для которых
имеются ограничения на время обмена с внешними устройствами при решении
задач. В специализированных ОС управляющие программы системы вместе с
интерпретатором языка управления часто размещают в постоянной памяти
(ПЗУ) и они всегда являются резидентными.
Требования ограниченного времени обработки прерываний в условиях
регулярной интенсивности потока прерываний для каждой группы устройств
приводит к необходимости сохранения всех оперативных программ вместе с
ядром ОС в главной памяти ВК. Задачи ОС могут быть разделены на
специализированные задачи или ВП ОС и проблемные задачи. Целью
специализированных задач является буферизация данных, сформированных для
низкоприоритетных задач, которые нуждаются в переключении режимов работы.
Специализированная ОС управления проектируется для работы в
следующих базовых режимах:
• инициализации или начальной загрузки;
• подготовки к оперативной работе;
• оперативной работы по управлению целевой системой;
• взаимодействия с другими компьютерами сети, обеспечивающими
технологическую подготовку и информирование о состоянии управляемого
технологического процесса.
Специализированная ОС должна решать следующие специальные
инициирующие задачи:
• начальное размещение управляющих данных в памяти;
• задачу тестирования состояния ВК и устройств системы управления;
• задачу реинициализации ОС при обнаружении особых ситуаций;
• задачу контроля целостности ОС и резидентных проблемных программ.
В режиме оперативной работы должны решаться следующие
специальные задачи:
Язык Ассемблера в программировании информационных и управляющих систем 245
• выдача управляющих сигналов на контроллеры чувствительных
элементов и исполнительных устройств;
• прием запросов на обслуживание отдаленных пользователей.
Программа-монитор, обеспечивает управление режимами и
функционирует во всех режимах. На нее возлагаются функции координации системы в
целом, а также, при необходимости, накопления ретроспективы состояний и
событий, происходящих в ВК и системе управления с привязкой к моментам
времени. Таким образом, специализированная ОС должна иметь многослойную
структуру с разными уровнями программ и программных интерфейсов:
общесистемные, специализированные и проблемные.
Внутренние управляющие структуры ядра определяют использование
ресурсов системы управления в разных режимах, задачах и ВП. Наиболее
распространено планирование работы внешних устройств в режиме тестов и в
режиме нормального обмена, а также задача разделения ресурса памяти или
внешнего устройства между ВП, которым мы уже уделили внимание в главе 6.
При фиксированной структуре и закреплении ресурсов за отдельными
задачами и ВП в малых ОС нет необходимости в создании таблиц
распределения ресурсов. Более того, для сокращения временных затрат в малых ОС при
достаточном объеме главной памяти для сохранения кодов и данных
оперативных задач удобно отказаться от динамического создания различных объектов
ОС. При этом используется только статическое создание объектов на этапе
генерации конкретной конфигурации ОС.
Структура любой ОС и процедуры для взаимодействия с ней
ориентируются на группу базовых объектов ОС. В минимизированной
специализированной ОС РВ используются следующие базовые системные объекты, доступные
системным программистам, ответственным за генерацию конкретной
конфигурации ОС:
• ВП или задачи, постоянно включенные в ОС к моменту их
эксплуатации, включая задачи пользователя фонового режима;
• очереди разных информационно-управляющих объектов, сохраняемые в
разных областях памяти;
• флаги готовности и управления ВП.
Кроме того, в состав ОС могут быть включены такие объекты
пользователя как:
• сообщения для обмена данными между ВП;
• семафоры для синхронизации ВП за событиями;
• сегменты для размещения относительно больших блоков информации.
Базовым принципом построения ОС с управлением по приоритетам ВП
является принцип учета важности внешних и внутренних событий для
функционирования всей системы. Чаще всего информация о внешних событиях
передается в систему управления в виде сигналов прерываний, которые
обрабатываются обработчиками прерываний, обеспечивающими, как правило,
быстрый обмен информацией. Эти обработчики формируют внутренние вторичные
события в информационной системе, для обработки которых главными
задачами используется оперативный резерв машинного времени системы. Именно эти
246 Глава 10 Управление решением задач и организация вычислительных процессов
события и являются основой для определения численных значений
приоритетов ВП.
Главное требование к функциональным вызовам ОС - они должны
иметь по возможности минимальное время выполнения системных функций.
Следствием этого требования является необходимость минимизации объема
управляющей таблицы ОС и минимизации программ ядра по критерию
минимума затрат времени на их выполнение.
Для эффективной реализации супервизора задач целесообразно
использовать команды обработки строк в форме машинного слова. Эффективность
воплощения программы в некоторой степени обеспечивается размещением
элементов таблицы в памяти по границам машинных слов и двойных слов, из-за
чего наиболее эффективной является структура элемента с четным или
кратным четырем количеством байтов. Для small-модели системы
программирования с единственным стековым сегментом, который выделен для сохранения
стеков всех задач, представление стекового адреса требует двух байтов. Для
представление приоритетного числа достаточно одного байта, а байт,
дополняющий структуру до четного можно использовать для хранения флагов событий
ВП, что облегчает конъюнктивный анализ группы из восьми событий. Готовой
считается задача, в которой в единицу установлены все флаги событий.
Управление программе выбора задачи передается после выполнения системной
функции, которая существенно меняет состояние задачи, то есть переводит ее в
состояние готовности. Основной функцией супервизора является сохранение
информации прерванного ВП и последующем выборе для выполнения в системе
наиболее приоритетного ВП. Для разрешения конфликтов при выборе из ряда
равноприоритетных готовых задач можно применять правило: при равных
приоритетах предпочтение получает задача, занесенная в таблицу первой.
Для обращения к планировщику задач необходимо генерировать
специальные последовательности команд на уровне обращений к ядру ОС в форме
макроопределений. Такой подход позволяет на основе этого ядра строить
библиотеку системных обращений для организации взаимодействия с ядром ОС, а
значит и любую унифицированную ОС, построенную на объектном (типа
irmx/86), процедурном или функциональном уровне.
10.2.3. Выбор приоритетов вычислительных процессов
и их взаимосвязь с системой прерываний
Выбор приоритетов в системах коллективного пользования
выполняется в соответствии с важностью пользователя, задачи, а также с учетом
возможности эффективной эксплуатации. Какой бы важной не была задача, решаемая
исключительно центральным процессором, если мы хотим обеспечить загрузку
внешних устройств, таких как дисковые накопители принтеры и т.д., то мы
вынуждены присваивать задачам, обслуживаемым этими устройствами, более
высокие приоритеты. В противном случае работа внешних устройств будет
полностью заблокирована задачами центрального процессора.
Для выбора приоритетов задач систем реального времени рассмотрим
перечень классов системных и проблемных ВП систем реального времени.
Язык Ассемблера в программировании информационных и управляющих систем 247
1. Прерывания:
• немаскированное прерывание для анализа угрозы отключения напряжения;
• прерывания от системного таймера;
• прирывания-запросы от схем контроля оборудования
многопроцессорной системы;
• прерывания от конечных выключателей и иных датчиков систем РВ;
• прерывания от абонентов сети.
2. Системные ВП и инициирующие события:
• ВП, выполняемые при запрете прерываний:
♦ обработка большинства прерываний;
♦ супервизор, обеспечивающий выбор наиболее приоритетного
готового ВП;
• ВП, выполняемые при разрешенных прерываниях:
♦ процесс реконфигурации системы РВ - инициируется
обработчиком прерываний от аппаратуры контроля, задач тестирования
или задачей оценки состояния оборудования;
♦ обслуживание временных интервалов задач управления,
инициируемых монитором в режиме оперативной обработки и
запросами проблемных задач;
♦ задача управления режимами выполнения приоритетных готовых
ВП (монитор либо супервизор);
♦ задача переключения режимов работы системы;
♦ процесс накопления очереди запросов на исполнение
управляющих воздействий;
♦ процесс режима коррекции информационной базы;
♦ фоновый процесс контроля и анализа свободного времени.
В ОС малых ВС задача-монитор может выполняться в составе задач,
которые обращаются к ОС с блокированием (запрещением) всех сигналов
прерываний на период ее выполнения. Предельная задержка времени в мониторе на
интервале запрета прерываний должна входить в суммарную оценку времени
обработки прерываний.
3. Проблемные ВП и события их инициации:
• задача управления - инициируется событием получения сигнала из
исполнительной части управляющей системы;
• задачи оперативных и прогнозирующих расчетов - инициируются
событиями начала следующего шага задачи управления;
• задача анализа текущего состояния системы РВ и ее оборудования;
• задача статистического анализа состояния оборудования системы РВ -
инициируется задачей накопления информации об ошибках;
• подзадачи или отдельные потоки вычислений основной задачи,
запускаемые из нее.
Наибольший приоритет во всех компьютерных системах имеют
процессы обработки прерываний, приоритетные отношения между прерываниями оп-
248 Глава 10 Управление решением задач и организация вычислительных процессов
ределяются на аппаратном уровне. Их фактическое изменение может быть
выполнено, либо перепрограммированием аппаратного БПП в режим
циклической смены приоритета, либо в режим автоматического изменения номера
прерывания с минимальным приоритетом (дна уровней прерываний) только
маскированием соответствующего прерывания ВП достаточного приоритета [1]
В малых ОС РВ в таких действиях чаще всего нет потребности. В
системах, которые реализуют разные режимы обработки в разных режимах
эксплуатации управляющей системы, переход из одного режима в другой чаще всего
осуществляется за счет запрета и маскирования прерываний при обработке
отдельных задач. Значения приоритетов выполняемых задач, как правило,
сохраняются, а для ненужных задач блокируются условия их возникновения.
Для выбора приоритетов конкретных задач необходимо ранжировать
события системы по уровню их важности и требованиям точности и
надежности обработки этих событий в системе. Для построения интегрированного
показателя важности необходимо учитывать временные и весовые показатели
важности. Полагая задачи с меньшим приоритетным числом менее
приоритетными, рассмотрим пример распределения значений приоритетов задач в
последовательности их убывания для некоторой системы РВ.
Высокоприоритетные системные задачи:
• Ofxh - автономные прерывания, при обработке которых разрешены
другие прерывания, к которым относятся немаскированные прерывания и
прерывания по отказам оборудования;
• Oexh - задачи информационного обмена между процессорами;
• Oelh - задача реконфигурации оборудования системы РВ;
• OeOh - задача общесистемной службы времени;
• Odxh - процесс системного монитора или супервизора;
• Ocxh - процесс переключения режимов работы ВК.
• Obxh - специальные встроенные задачи обмена и вспомогательных
процессов кратковременного переключения режимов.
Проблемные задачи:
• Oafh - задача формирования управляющего воздействия по ближайшему
информационному сигналу;
• Oaeh - задача оперативного расчета;
• Oadh - задача прогнозирующего расчета;
• Oadh - задача программы-монитора;
• 90h - задача управления объектом или выдачи управляющих воздействий;
• 8xh - задачи взаимодействия с рабочими станциями сети;
• 7xh - 2xh - проблемные задачи фонового режима.
• Низкоприоритетные системны процессы.
• lxh - задача текущего контроля и тестирования свободных устройств;
• Oxh - задачи низшего уровня, с учетом свободного времени процессора
и ожидания прерываний в режиме HLT.
Эти значения могут быть изменены с достаточной гибкостью с учетом
имеющихся степеней свободы в определении приоритетов.
Язык Ассемблера в программировании информационных и управляющих систем 249
Из изложенного вытекает, что задачи требующие жестко
фиксированного предельного интервала времени между получением исходных и выдачей
управляющих воздействий можно решать только следующим образом.
Сначала выполнить предварительную обработку для имеющихся данных в рамках
одной из высокоприоритетных задач. Затем в обработчике прерываний
получить новые значения контролируемых параметров, в кратчайшее время
рассчитать управляющее воздействие и выдать его на объект управления. Инициация
прерываний такого типа может осуществляться системой ввода-вывода,
таймером или сигналом запроса управляющей информации от внешнего устройства.
Таким образом, в системы РВ часто используют обработчик прерываний
такого комплексного типа.
Если на системы РВ возлагается задача сбора информации о
характеристиках объектов, получаемых через аналоговые датчики и преобразователи, то
при вводе информации необходимо выполнить комплекс действий,
включающих предварительную обработку с линеаризацией выхода датчиков по их
характеристикам. Кроме того, часто необходим контроль достоверности
результатов измерения их преобразования и сглаживание, устраняющее влияние
шумов и помех. Вводимая информация накапливается в отдельном буфере по
каждому каналу, а текущая - в объединенном буфере для всех каналов. Если
алгоритмы преобразования данных только обновляют элементы объединенного
буфера, то при достаточной частоте преобразований и плавности изменения
сигналов замена старого значения на новое будет практически незаметна. Более
того, потеря одиночных достоверных значений при формированиии
объединенного буфера не снизит качества обработки, если она выполняется поочередно
для всех датчиков.
Алгоритмы линеаризации или тарировки датчиков по
кусочно-линейному методу строятся на основе линейного поиска в отсортированном массиве
эталонов. Они используют сложения и умножения и переводят коды
измерительных устройств в физические или масштабированные значения. Процедуры
фильтрации могут быть более разнообразными, а вычисления по линейным
формулам могут быть с достаточной точностью реализованы сложениями и
сдвигами.
С другой стороны изменение информации по какому-либо из каналов
может инициировать логическую обработку в системе управления начиная с
анализа первичных событий и кончая коррекцией управляющих воздействий
следующей группой команд.
CMP AX,[BX] ; Контроль изменения значения
JZ LeCorr
XCHG АХ,[ВХ] ; Регистрация нового значения
MOV Li[BX],l ; Фиксация признака изменения.
LeCorr: . . .
Реализация критических фрагментов управляющих программ на
Ассемблере может обеспечить реализацию довольно жестких требований по
скорости в управляющих системах и их регистраторах. Рассмотренную группу
программ относят к специальному обеспечению СРВ.
Глава 10 Управление решением задач и организация вычислительных процессов
10.5. Организация защищенности программ и данных
К другой группе важнейших организационных аспектов
многозадачности относят разделение данных и кодов разных задач от непроизвольных или
умышленных взаимодействий и эффективное совместное использование кодов
и управляющих данных, общих для разных задач. Многие прикладные
программы могут работать эффективнее, если используют для своих буферов
больший объем, но могут оказаться неработоспособными, если в ВК не окажется
достаточного объема памяти.
Динамическое выделение памяти включено как базовая функция не
только в языки программирования высокого уровня, но и в операционные
системы и их расширения. Для динамического управления памятью в MS DOS
используются следующие функции прерывания int 21h:
• 48h - выделение сегмента памяти: в ВХ - требуемое число 16-байтовых
параграфов; сегментный адрес (селектор реального режима)
выделенного блока засылается в АХ, размер наибольшего доступного блока в
параграфах - в ВХ, С1=1 - в случае ошибки, код ошибки - в АХ;
• 49h - освобождение блока памяти: в ES - начальный сегментный адрес
(селектор) освобождаемого блока; cf=l - в случае ошибки, с кодом
ошибки в АХ;
• 4ah - изменение выделенной длины сегмента памяти: в ES - начальный
сегментный адрес изменяемого блока, в ВХ - требуемое число
16-байтовых параграфов; cf=l - в случае ошибки, с кодом ошибки в АХ.
Особенность эксплуатации этих функций MS DOS состоит в том, что
после загрузки программы ей выделяется практически вся свободная память и для
возможности управления ее дальнейшим выделением, сначала необходимо
освободить ее незадействованную часть функцией 4ah и только после этого выделять
блоки требуемого размера. Другой недостаток этих функций связан с тем, что
они работают только в пределах 640 Кбайтов памяти, выделяемых для MS DOS и
совместимых с ней ОС. Еще один недостаток выделения сегментов в реальном
режиме связан с тем, что защита от случайного или преднамеренного доступа к
чужим областям памяти работает только с границей сегмента длиной 64 К.
Функции динамического управления памятью языков высокого уровня,
например, malloc и free и языке С, гораздо более удобны. В зависимости от
выбранной модели памяти и выбранного расширителя DOS или более мощной
ОС защищенного режима может автоматически обеспечивать доступ к полной
памяти системы. Простая возможность подключения таких расширителей
возможна в ряде транслирующих систем, например Watcom С. Расширители по
существу начинают выполнение программы под DOS, обеспечивают переход в
защищенный режим и фактически выполняют программу в защищенном
режиме, после чего опять возвращаются в DOS с восстановлением конфигурации
системы.
Среди всех протоколов доступа к памяти, позволяющих решить
проблему выхода за 640 Кбайтов в MS DOS следует отметить XMS (Extended Memory
Specification), реализуемых драйвером Microsoft HIMEM.SYS и EMS (Expanded
Язык Ассемблера в программировании информационных и управляющих систем 251
Memory Specification), реализуемый драйверами при наличии специальной
платы расширения памяти или ее эмулятора. Протокол XMS удобен только для
управления выделением и учета сегментов полной памяти, работы по прямым
адресам только с первым сегментом верхней памяти под DOS и переброса
(swapping) сегментов при переключении образов экранов и организации
виртуальной памяти. На этот протокол опираются многие прикладные и системные
программы реального режима.
Протокол EMS хотя и позволяет без дополнительных пересылок
работать с верхней памятью по прямому адресу, но для более эффективной
реализации требует специальной аппаратуры переключения сегментов, сейчас
практически не выпускаемой. Такая аппаратура по аналогии с расширением
адресного пространства в процессорах PDP-l l фирмы DEC позволяет в части
адресного пространства между 640 К и I M получить несколько окон доступа к памяти
лежащей за пределами I M. Наиболее распространенные драйверы памяти,
быстро эмулирующие этот протокол в виртуальном режиме носят названия
EMM386.SYS и QEMM.SYS и включают в себя дополнительные возможности
интерфейса VCPI (Virtual Program Control Interface) со страничной
организацией памяти.
Подобные функции выделения памяти в защищенном режиме построены
в соответствии с интерфейсом защищенного режима DOS - DPMI (DOS
Protected Mode Interface). Этот интерфейс содержит достаточно полный набор
функций для построения расширителей DOS (DOS-expander) позволяющих
полностью использовать возможности защищенного режима, не теряя
окружения DOS.
Для гарантированного разделения данных и кодов необходимо
использовать гораздо больше информации о сегменте, чем это возможно в реальном
режиме. Поэтому в сегментные регистры (см. рис. 3.2) вместо адреса начала в
защищенном режиме загружается так называемый селектор, в котором
хранится указатель на восьмибайтный блок памяти, называемый дескриптором, в
котором хранится вся нужная информация о сегменте. При выполнении команд
загрузки сегментных регистров содержимое дескриптора для получения
нормальной скорости работы процессора переносится в теневой регистр,
связанный с сегментным, но непосредственно недоступный программисту.
Эти блоки хранятся в сегментах памяти, называемых таблицами
дескрипторов: глобальной - GDT, локальной - LDT, определяемой для каждой
задачи и прерываний - IDT, замещающей таблицу векторов прерываний
реального режима. Селектор содержит относительный адрес в таблице (биты а, см.
рис. 3.2); бит типа таблицы: 1=0 - для LDT и 1=1 - для GDT, а также
двухбитовое поле запрашиваемого уровня привилегий для контроля правильности
доступа в механизме защиты. Соотношение запрашиваемого и предоставляемого
уровня привилегий определяют 4 кольца защиты: 00 - кольцо ядра ОС, 01 -
кольцо обслуживания аппаратуры, 10 - кольцо систем программирования
управления базами данных и расширениями ОС, 11 - кольцо прикладных
программ пользователя.
Поля дескриптора (см. рис. 10.1) используются со следующим
назначением:
252 Глава 10 Управление решением задач и организация вычислительных процессов
1. Базовый адрес сегмента определяет начальный линейный адрес
сегмента и занимает байты 2, 3, 4 и 7 дескриптора. Он определяет произвольный
начальный адрес сегмента в линейном адресном пространстве размером 4
Гбайтов и таким образом позволяет работать со всей физически адресуемой
памятью процессора и предотвращать обращения к несуществующей памяти.
2. 20-битовое поле предела определяет границу сегмента и занимает
байты 0 и 1, а также младшие 4 бита байта 6 дескриптора. Предел равен размеру
сегмента в единицах гранулярности минус 1, что задает последний адресуемый
элемент в сегменте. Различают байтовую G=0 и страничную G=l
гранулярность, определяемую старшим битом байта 6. В процессоре 80286 все сегменты
имеют байтовую гранулярность и G=0. При страничной гранулярности память
выделяется страницами по 4 Кбайта и когда поле предела содержит 0FFFFFH,
то получается сегмент размером 4 Гбайта. Термин "гранулярность" можно
рассматривать в соответствии с понятием разрешающей способности цифрового
представления изображений. Поле предела позволяет аппаратно
контролировать выход исполнительного адреса за пределы сегмента, а при учете размеров
и разме щения сегментов позволяют исключить их перекрытия. Однако
возможность перекрытий и повторных подключений сегментов к другим
определяется проектировщиком системной управляющей программы, который может
предусмотреть просмотр и дублирование доступа к памяти пользователя
средствами привилегированного режима.
1
3J
1
Байг7
База (31..24)
Байт 3
Байт 6 48
| GD0U Предел |
Байт 2 16
База сегмента (15..0) |
47
I
15
1
Байт 5 (AR)
р DPL s tttA
Байт 1
1
Бай г 4
База (23.. 16)
БайтО
Предел сегмента (15..0)
32
I
0
1
Рис. 10.1. Общая структура дескрипторов в процессорах ix86
3. В байте 5 дескриптора (AR - Access Rights) закодированы права
доступа к сегменту. Бит присутствия р дает возможность управления виртуальной
памятью: р=1, когда описанный сегмент присутствует в физической памяти, и
р=0, когда он перемещен на диск. Обработка особого случая при р=0 позволяет
организовать замещение сегментов в главной памяти, называемое свопингом
или "подкачкой". При р=0 процессор игнорирует остальные поля дескриптора
за исключением проверки байта прав доступа, что позволяет программисту
записать в остальных байтах информацию о сегменте, сохраненном на диске.
Двухбитовое поле уровня привилегий сегмента DPL определяет
допустимый уровень привилегий при обращении к сегменту со значениями от 0
(наибольшие привилегии) до 3 (наименьшие привилегии). Бит s=0 определяет
системный объект, который может и не быть сегментом памяти.
Трехбитовое поле типа ТУРЕ определяется целевым использованием
сегмента, ограничивая допустимые операции с его данными. Двоичными
кодами определены следующие типы сегментов:
• 000 - только считываемый сегмент данных;
• 001 - сегмент данных с разрешением считывания и записи;
Язык Ассемблера в программировании информационных и управляющих систем 253
• 010 - только считываемый сегмент стека (практическое использование
бессмысленно);
• 011 - сегмент стека с разрешением считывания и записи;
• 100 - сегмент кода - разрешено только выполнение;
• 101 - сегмент кода с разрешенным выполнением и считыванием;
• 110 - подчиненный сегмент кода - разрешено только выполнение;
• 111 - подчиненный сегмент кода с разрешенным выполнением и
считыванием.
Тип ограничивает использование сегментов только их предварительным
назначением. Программы не могут модифицироваться в процессе выполнения,
программные сегменты можно защитить от считывания, так как постоянные и
управляющие данные не должны изменяться. Старший бит типа различает
сегменты кода и данных. Средний называется битом подчинения, младший -
определяет возможность считывания кода как данных с помощью префикса замены
сегмента.
Тип сегмента 0 соответствует сегменту данных, расположенному в
виртуальном постоянном запоминающем устройстве (ПЗУ). Будет ли он
соответствовать фактическому сегменту ПЗУ, не играет роли, процессор, начиная с 1386,
запрещает запись в адресное пространство такого сегмента. Очевидно, этот тип
следует назначать сегментам, содержащим таблицы констант, общесистемные
справочные данные, информацию о состоянии системы, объектов и т.п. Тип
сегмента 1 задает традиционный сегмент данных и, так же как и сегмент типа О
не допускает переход к выполнению его содержимого командами CALL FAR
или JMP FAR, что вполне допустимо в процессоре 8086, хотя и бессмысленно.
Такое ограничение способствует разработке надежных и живучих ОС.
Типы сегментов 010 и 011 определяют сегменты стека, которые по
существу являются разновидностью сегментов данных с такими же операционными
ограничениями. В сегментах стека поле предела определяет нижнюю границу
неадресуемой области сегмента, так что все смещения стека должны быть
строго больше предела, а если смещение меньше или равно пределу, фиксируется
особый случай нарушения стека.
Попытка нарушить любое из правил, заданных типом, вызывает особый
случай защиты, однако эти ограничения легко может обойти разработчик
системной программы, создав дублирующие дескрипторы с другим типом и
другими правами доступа. Бит использования или обращения А процессор
устанавливает в 1 при очередном обращении к сегменту памяти, что позволяет
выбрать нечасто используемые сегменты для передачи на диск в процессе обмена
страниц виртуальной памяти.
Комбинации ООН и ЗОН в байте прав доступа считаются
недействительными, однако дескрипторы сегментов с неверными или неопределенными
значениями в байте 5 сами по себе не являются ошибкой, но при попытке работы с
таким дескриптором в процессоре возникает особый случай.
В старшей тетраде байта 6 находятся еще 2 управляющих бита. Бит
размера по умолчанию D=0 обеспечивает совместимость с процессором 80286,
работая с 16-битовыми данными, а в противном случае используются 32-битовые
данные. То есть работа в 32-разрядном защищенном режиме возможна без до-
254 Глава 10 Управление решением задач и организация вычислительных процессов
полнительных префиксов в командах. Бит пользователя U предназначен для
использования системы и процессор не использует этот бит. Возможны его
применения для отметки сегментов при "сборе мусора" или для определения
сегментов с запретом модификации базовых адресов.
10.4. Структуры данных защищенного режима
В предыдущем параграфе мы уже рассмотрели одну из базовых структур
данных защищенного режима, названную дескриптором сегмента. Он
используется при контроле адреса каждой из адресных команд и контролирует
корректность обращения к соответствующему сегменту. В начале этой главы мы
уже рассмотрели особенности мультизадачности на примере построения
супервизора многозадачной системы с управлением по приоритетам. При этом
компьютер обслуживает задачи-пользователи последовательно, выделяя им
временные кванты вплоть до следующего обращения к супервизору.
Обычно сохранение состояния выполняющейся задачи требует сохранения
содержимого всех регистров процессора и некоторых переменных в памяти.
Кроме того, нужно сохранить и адрес той команды, которая должна выполняться
после рестарта (перезапуска) задачи, что в нашем примере делалось с помощью
записи в стек. Такое зафиксированное состояние задачи называют контекстом ее
выполнения, а действия процессора по "фотографированию" состояния одной
задачи и рестарту другой называются переключением контекста. Как недостаток
рассмотренного решения было отмечено использование стека прерываемой
программы для системных нужд.
В компьютере с разделением времени выделяется область памяти,
доступная только ОС, в которой хранится вся информация, необходимая для
рестарта задачи. Такую область называют контекстной памятью или кадром
состояния (state frame). Для минимизации времени на переключение контекста
следует сохранять и восстанавливать минимальную информацию о каждой
задаче. Всем задачам в системе выделяется область основной памяти (раздел),
чтобы не привлекать для переключения контекста относительно медленную
дисковую память. В таком подходе требуется сохранять только содержимое
внутренних регистров процессора, т.е. сравнительно небольшой объем
информации. Однако при этом компьютер должен обладать основной памятью
значительной емкости.
В процессорах, начиная с i286, задача определяется как совокупность
кода и данных, которым назначен сегмент состояния задачи (Task State
Segment - TSS) [*4]. Другими словами, имеется однозначное соответствие
между сегментами TSS и задачами. По существу, TSS является эквивалентом
контекстной памяти, в которой хранится информация о преостановленной задаче,
из которого она восстанавливается при рестарте. Сегмент TSS следует считать
небольшим сегментом данных с разрешенными операциями считывания и
записи, доступ к которому не разрешается никаким программам, даже на уровне
привилегий 0. К сегментам TSS может обращаться только сам процессор при
передаче управления.
Язык Ассемблера в программировании информационных и управляющих систем 255
Как и другие сегменты памяти, сегмент TSS определяется дескриптором
сегмента, который может находиться только в глобальной дескрипторной
таблице GDT и в котором в отличие от дескриптора, показанного на рис. 10.1
старшие 4 бита байта 6 имеют значение 000U, а полубайт типа - 10В1. Он
похож на дескриптор сегмента кода и содержит обычные для дескриптора
сегмента поля базового адреса и предела.
Бит занятости В в поле типа показывает, занята задача или нет. Занятая
задача выполняется сейчас или ожидает выполнения. Поле типа со значением 9
показывает неактивную задачу; значение 11 показывает занятую задачу.
Задачи не являются реентрантными. Процессоры, начиная с i286, используют бит
занятости для обнаружения попытки вызова задачи, выполнение которой
прервано.
Поля базы, предела, DPL, а также биты гранулярности G и присутствия
Р выполняют функции, аналогичные их использованию в дескрипторах
сегментов данных. Поле предела должно содержать значение, большее или равное
67Н, что на один байт меньше минимального размера состояния задачи. При
попытке переключения на задачу, чей дескриптор TSS имеет предел меньше
67Н, возникает особый случай. Когда применяется двоичная карта разрешения
ввода-вывода (см. далее), предел должен быть больше. Больший предел также
потребуется и ОС, если она сохраняет в сегменте TSS дополнительные данные.
Oh/
4h/
8h/
32
16 15
О
Oh
2h
OCh/
4h
lOh/
14h/
18h/
6h
8h
OAh
OCh
ICh/
20h/
0 >
ESPO ->
0
ESP1 >
0
ESP2 ->
0 >
Обратная связь
SPO
sso
SP1
SSI
SP2
SSI
CR3
OEh
Регистры рис. 2. L
Рис. 10.2. Общая структура TSS
Обращение к дескриптору TSS не предоставляет процедуре возможность
считать или модифицировать дескриптор. Эти операции можно осуществить,
только применяя дескриптор данных, отображенный на ту же область памяти.
Загрузка дескриптора TSS в сегментный регистр приводит к особому случаю.
Дескрипторы TSS могут размещаться только в глобальной дескрипторной
таблице GDT. Попытка обратиться к TSS, используя селектор с установленным
256 Глава 10 Управление решением задач и организация вычислительных процессов
битом 11, который указывает на использование текущей LDT, вызывает особый
случай.
Формат 32- и 16-битового сегментов состояния задачи TSS полностью
включающий программно-доступные регистры с рис. 2.1., приведен на рис. 10.2.
Как видно из рис. 10.2, значительная часть TSS отведена для хранения
внутренних регистров процессора, причем восемь регистров общего
назначения хранятся в той же последовательности, в какой их включают в стек
команды PUSHAD и PUSHA, но в обратном порядке. Сохраняется содержимое
регистров CS и EIP, определяющее, с какой команды осуществляется рестарт
задачи. Сохранение в TSS содержимого регистра EFLAGS гарантирует правильное
действие команд условных переходов после рестарта задачи.
В сегменте TSS имеется также несколько дополнительных полей. Поле
обратной связи (link) представляет собой селектор TSS той задачи, которая
выполнялась перед данной задачей. С его помощью организуется трасса цепи
вложенных задач, запоминающие вложенные вызовы программ. Поле базы
двоичной карты разрешения ввода-вывода содержит 16-битное в данном сегменте
TSS, с которого начинается сама двоичная карта разрешения ввода-вывода.
Если это поле содержит 0, двоичная карта разрешения ввода-вывода отсутствует.
Бит ловушки Т (Trap) применяется для отладки; когда он содержит 1, при
переключении на данную задачу генерируется особый случай отладки,
вызывающий прерывание с вектором 1.
Когда процессор i386 начинает выполнение новой задачи [3,4], он
считывает всю информацию из первых 104-х байтов сегмента TSS. Большая часть ее
копируется в соответствующие регистры, причем загрузка сегментных
регистров вызывает пересылку дескрипторов нужных сегментов в теневые регистры.
После этого выполняется команда, адресуемая регистрами CS:EIP. Таким
образом, переключение на новую задачу похоже на выполнение команды FAR
RET возврата из подпрограммы, но как бы с восстановлением из стека
большего числа регистров.
До перехода в мультизадачный режим необходимо определить
дескрипторы TSS, разместить сами сегменты TSS в адресном пространстве и правильно
инициализировать их. При этом приходится учитывать, что сегменты TSS не
являются обычными сегментами памяти и невозможно загрузить селектор TSS
в сегментный регистр без генерирования особого случая нарушения защиты.
Следовательно, для работы с сегментами TSS следует пользоваться
альтернативным именованием, т.е. псевдонимами этих сегментов.
Если определить все сегменты TSS в соседних областях памяти, то для
инициализации их достаточно псевдонима одного сегмента данных,
включающего в себя все сегменты TSS. Однако удобнее создать по одному псевдониму
для каждого сегмента TSS с одинаковыми полями базового адреса и предела.
Управление псевдонимами упрощается, если чередовать в глобальной дескрип-
торной таблице GDT дескрипторы TSS и их псевдонимы, чтобы всегда знать о
принадлежности псевдонима к конкретному сегменту TSS.
При первом запуске задачи начальное содержимое сегмента TSS может
повлиять на ее поведение. Регистры CS и EIP должны адресовать первую
команду, а сегментные регистры данных должны содержать селекторы сегментов
Язык Ассемблера в программировании информационных и управляющих систем 257
данных задачи. В регистр SS следует загрузить селектор сегмента стека с
правильным уровнем привилегий. Регистры общего назначения обычно содержат
нули, если первый запуск задачи не требует их конкретных начальных
значений. При необходимости нужно инициализировать в TSS поля SSiiESPi. Если
задача рассчитана на использование локальной дескрипторной таблицы LDT и
страничного преобразования, в сегменте TSS потребуется инициализировать
поля LDTR и CR3.
Отметим, что в сегменте TSS отсутствуют поля для регистров CR0 и
CR2. Это означает, что их содержимое не изменится при переключениях задач.
Следовательно, страничное преобразование и условия работы с устройством с
плавающей точкой являются глобальными для всех задач. Для каждой задачи
может быть свой каталог страниц (база которого определяется регистром
CR3), но страничное преобразование может быть разрешено или запрещено
только для всей системы. Переключения задач не затрагивают регистры GDTR
и IDTR, а также регистры отладки и проверки. Однако у каждой локальной
задачи может быть своя дескрипторная таблица LDT.
10.5. Управление переключением задач в
защищенном режиме
Отправной точкой работы процессора является подача на него сигнала
сброс, после которой начинается выполнение программ BIOS в реальном
режиме. Для перехода в защищенный режим можно воспользоваться средствами
того же BIOS и протокола DPMI, предварительно подготовив таблицы и
базовую конфигурацию задач защищенного режима Особую роль в управлении
режимами работы играют так называемые системные управляющие регистры,
тестовые регистры или регистры проверки, а также регистры отладки.
В группу регистров управления [*4] входят четыре регистра: CRO, CR1,
CR2 и CR3. CR0 содержит поле битов управления и состояния. Младшие 16
разрядов называются словом состояния машины MSW (Machine Status Word -
мнемоника впервые введена в i286) и предназначены для обеспечения
совместимости с защищенным режимом i286.
Принято следующее назначение разрядов CR0.
• РЕ (Protect Enable) - разрешение защищенного режима работы МП. При
РЕ=0 включен реальный режим работы Защищенный режим может
быть установлен при загрузке регистра MSW командой LMSW или
MOV CR0,...; а сброшен только командой MOV CR0,...
• МР (Math Present) - наличие сопроцессора. Операционная система
устанавливает МР= 1, если математический сопроцессор (i287 или i387)
присутствует (для i486 должно быть МР=1).
• ЕМ (Emulation) - задает переход в режим эмуляции при недоступном
сопроцессоре.
• TS (Task Switch) - индикатор переключения задачи, предназначенный
для переключения сопроцессора с плавающей точкой при переключении
задач.
258 Глава 10 Управление решением задач и организация вычислительных процессов
• ЕТ (Extension Type) - определяет режим работы математического
сопроцессора.
• NE (Numeric Error) - определяет режим обработки численных ошибок.
• WP (Write Protect) - WP= 1 защищает от записи страницы пользователя.
• AM (Alignment Mask) - АМ=1 задает режим контроля выравнивания
операндов на соответствующую границу.
• NW (Not Write-thro ugh) - определяет режим работы кэш-памяти.
• CD (Cash Disable) - запрещает использование внутренней кэш-памяти.
• PG (Paging) - задает режим страничного преобразования.
Регистр CR1 пока является запрещенным, а регистры CR2 и CR3
используются при работе в режиме страничных преобразований. Регистры
проверки TR3 - TR7 предназначены для контроля правильности работы
ассоциативной кэш-памяти. Регистры отладки DR0 - DR4 позволяют задать 4
контрольных точки останова, регистр DR7 задает режим управления
контрольными точками, a DR6 - состояние контрольных точек на момент прерывания.
Для организации переключения задач применен широко
распространенный метод логических машин управления (ЛМУ), который можно
рассматривать как частный случай рассмотренного ранее метода виртуальных машин в
программной реализации на инструментальных ЭВМ. Этот метод обеспечивает
широкую экспериментальную базу для исследований в области разработки
архитектурных и структурных методов решения системных и пользовательских
задач. Основу его аппаратно-программной реализации в процессорах ix86
составляют команды JMP, CALL и IRET, бит NT регистра флагов, а также
прерывания и особые случаи. Управляющая информация для таких команд
находится в поле типа дескриптора.
Для перехода от задачи к задаче при управлении мультизадачностью,
начиная с процессора 1386 используются команды [*4] межсегментной передачи
управления - переходы и вызовы. Кроме того, задача может активизироваться
прерыванием и особым случаем. Когда реализуется одна из этих форм
передачи управления с назначением, определяемым элементом в одной из дескриптор-
ных таблиц. Тип дескриптора может быть таким, который инициирует
выполнение новой задачи после сохранения состояния текущей задачи. Имеются два
кода типов, определяющих дескрипторы задач, находящихся в дескрипторной
таблице: дескрипторы сегментов состояния задачи (TSS) с кодом типа 10В1 и
шлюзы задачи (task gate) с кодом типа 0101. Когда управление передается
любому из дескрипторов этих типов, происходит переключение задачи.
Большая часть системных дескрипторов (бит s=0) определяет не
сегменты, а шлюзы, используемые как посредники при переключениях в соответствии
с кодом младшего полубайта байта доступа дескриптора:
0000-резерв;
0001 - свободный TSS 16-битового режима;
0010 - сегмент LDT;
ООП - занятый TSS 16-битового режима;
0100 - шлюз вызова 16-битового режима;
0101 - шлюз задачи;
ОНО - шлюз прерывания 16-битового режима;
Язык Ассемблера в программировании информационных и управляющих систем 259
0111 - шлюз ловушки 16-битового режима;
1000-резерв;
1001 - свободный TSS 32-битового режима;
1010-резерв;
1011 - занятый TSS 32-битового режима;
1100 - шлюз вызова 32-битового режима,
1101 -резерв;
1110 - шлюз прерывания 32-битового режима;
1111 - шлюз ловушки 32-битового режима;
i ► признак шлюза;
1 ► признак 32-битового режима.
Дескрипторы шлюзов хранят только заполненные байты прав доступа и
селектор соответствующего объекта в глобальной таблице дескрипторов,
помещенный на место двух младших байтов базового адреса.
При каждом переключении задачи процессор может перейти к другой
локальной дескрипторной таблице LDT, что позволяет назначить каждой
задаче свое отображение логических адресов на физические. Это дополнительное
средство защиты, так как задачи можно изолировать и предотвратить их
взаимодействие друг с другом. Производится также перезагрузка базового регистра
каталога страниц PDBR, что позволяет применить для изолирования задач
механизм страничного преобразования.
При переключении задачи с помощью прерывания или особого случая
происходит автоматический возврат к прерванной задаче. Там, где требуется
исключительно быстрая реакция на прерывание, время на сохранение
состояния процессора может оказаться слишком большим. Возможным
компромиссом в таких ситуациях является применение связанных с задачами структур
данных и программного переключения задач, что позволяет сохранять меньше
информации о состоянии процессора.
При рассмотрении действий процессора в ходе переключения задачи
удобно пользоваться терминами уходящая задача (outcoming task) или старая
задача (old task) и приходящая задача (incoming task) или новая задача (new
task). Под ними понимается, соответственно, та задача, выполнение которой
прекращается, и та задача, которую будет выполнять процессор.
Переключение задачи в процессоре i486 могут вызывать четыре
системных события:
• уходящая задача выполняет команду FAR CALL или FAR JMP, а
селектор выбирает шлюз задачи;
• уходящая задача выполняет команду FAR CALL или FAR JMP, а
селектор выбирает дескриптор TSS;
• уходящая задача выполняет команду IRET для возврата в предыдущую
задачу; эта команда приводит к переключению задачи, если только в
регистре EFLAGS бит NT вложенной задачи находится в состоянии 1;
• возникло аппаратное или программное прерывание, а соответствующий
элемент дескрипторной таблицы прерываний IDT содержит шлюз задачи.
260 Глава 10 Управление решением задач и организация вычислительных процессов
Команды JMP, CALL и IRET, а также прерывания и особые случаи
являются обычными механизмами процессоров ix86, которые можно применять и
без переключения задач [*3]. Тип дескриптора (когда вызывается задача) или
флажок NT (при возврате из задачи) различают стандартный механизм и
форму переключения задачи. Процедура обслуживания прерывания всегда
возвращает управление прерванной программе. Если флажок NT сброшен в 0,
производится обычный возврат, а если он установлен в 1, происходит переключение
задачи. Задача, участвующая в этом переключении, определяется селектором
TSS в сегменте TSS процедуры обслуживания прерывания.
Переключение задачи состоит из следующих действий, выполняемых
одной из команд JMP FAR, CALL FAR и IRET при NT= 1.
1. Проверить, что уходящей задаче разрешено переключение на новую
задачу. К командам JMP и CALL применяются правила привилегий
обращения к данным. Поля DPL дескриптора TSS и шлюза задачи должны быть
больше или равны обоим CPL и RPL селектора задачи. Особым случаем,
прерываниям и командам IRET разрешается переключать задачи независимо от полей
DPL шлюза задачи или дескриптора TSS назначения.
2. Проверить, что дескриптор TSS приходящей задачи отмечен как
присутствующий и имеет правильный предел (не меньше 67Н). Любые ошибки до
этого момента возникают в контексте выходящей задачи. Эти ошибки
восстанавливают все изменения, произошедшие в состоянии процессора, когда
сделана попытка выполнить команду, приводящую к ошибке. Это позволяет адресу
возврата обработчика особого случая адресовать команду, сформировавшую
ошибку, а не находящуюся за ней команду. Обработчик особого случая может
исправить условие, вызвавшее ошибку, и осуществить рестарт задачи.
Вмешательство обработчика особого случая может быть совершенно незаметно для
прикладной программы.
3. Сохранить состояние уходящей задачи. Процессор берет базовый
адрес текущего сегмента TSS из регистра задачи. Регистры процессора
копируются в текущий TSS (это восемь регистров общего назначения, шесть сегментных
регистров и регистр EFLAGS).
4. Загрузить в регистр TR селектор дескриптора TSS входящей задачи,
установить бит занятости В= 1 для новой задачи и установить бит TS
переключения задачи в регистре CRO. Селектор либо является операндом команды JMP
или CALL, либо берется из шлюза задачи.
5. Загрузить состояние входящей задачи из ее сегмента TSS и
продолжить выполнение. Загружаются следующие регистры: регистр LDTR, регистр
EFLAGS, восемь регистров общего назначения, регистр EIP и шесть
сегментных регистров. Все ошибки, обнаруживаемые на этом этапе, возникают в
контексте приходящей задачи. Для обработчика особого случая первая команда
приходящей задачи считается невыполнимой.
Отметим, что при переключении задачи всегда сохраняется состояние у-
ходящей задачи. Если задача возобновляется, выполнение начинается с той
команды, которая была бы следующей. Регистры восстанавливаются на
значения, которые они содержали при прекращении выполнения задачи.
Язык Ассемблера в программировании информационных и управляющих систем 261
Каждое переключение задачи устанавливает бит TS переключения
задачи в регистре CR0. Этот бит полезен для системных программ для
координации операций центрального процессора и сопроцессора с плавающей точкой.
Бит TS показывает, что контекст сопроцессора отличается от контекста
текущей задачи.
Шлюзы задачи и вызова используются не как сегменты памяти, а как
посредники в процессе переключения задачи. Селектор задачи разрешается
адресовать в обычных командах CALL FAR и JMP FAR, которые в этом случае
инициируют переключение задачи. Текущая программа должна быть
достаточно привилегирована для доступа к шлюзу задачи; правило привилегий
аналогично соответствующему правилу для шлюза вызова: Max (CPL, RPL) ^ DPL
шлюза задачи, где CPL - текущий уровень привилегий уходящей задачи, RPL -
требуемый уровень привилегий в соответствии с селектором, заданным в шлюзе.
Для переключения задачи в межсегментных командах передачи
управления разрешается использовать селектор дескриптора TSS вместо селектора
шлюза задачи. Другими словами в командах CALL FAR и JMP FAR можно
указать селектор шлюза задачи (косвенное переключение задачи), не привлекая
шлюз задачи. Правило защиты по привилегиям в этом случае принимает вид:
Max (CPL, RPL) < DPL сегмента TSS. Это правило аналогично предыдущему,
но теперь процессор проверяет другой дескриптор.
При переключении задачи процессор должен сохранить контекст
уходящей задачи в ее сегменте TSS и загрузить контекст приходящей задачи из
соответствующего сегмента TSS. После этого начинается выполнение входящей
задачи с команды, адресуемой содержимым восстановленных регистров CS:EIP.
Сегмент состояния входящей задачи идентифицируется дескриптором TSS,
селектор которого прямо или косвенно (через шлюз задачи) определяется при
переключении задачи. Контекст уходящей задачи сохраняется в сегменте TSS,
селектор дескриптора всегда находится во внутреннем регистре задачи TR (Task
Register). Поэтому сначала регистр сохраняет контекст уходящей задачи в
сегменте TSS, выбираемым регистром TR, а затем загружает в регистр TR селектор
дескриптора TSS приходящей задачи. Следовательно, процессоры начиная с i386
оперирует регистром TR автоматически.
Отметим, что содержимое регистра TR любая программа с помощью
команды STR (Store Task Register) может передать в регистр или ячейку
памяти, но обычно эта информация бесполезна и может использоваться только для
отладки. Для инициализации регистра TR применяется привилегированная
команда загрузки регистра задачи LTR (Load Task Register), а после этого
процессор управляет регистром TR автоматически. После загрузки нового
контекста из сегмента TSS и модификации регистра TR процессор отмечает сегмент
TSS приходящей задачи как занятый, устанавливая в 1 бит занятости В в его
дескрипторе.
Остановимся подробнее на использовании привилегий задач. Кроме
контроля допустимости переключений, они контролируют допустимость
использования сегментов данных и стека, в принципе те же условия, что и при
переключении задач используются и при работе с данными и стеком для
упрощения описаний EPL = Max (CPL, RPL) называют эффективным или действую-
262 Глава 10 Управление решением задач и организация вычислительных процессов
щим уровнем привилегий. Этот уровень сравнивается с дескрипторным уровнем
привилегий, запрещая работу с недостаточным уровнем привилегий путем
проверки истинности отношения EPL ^ DPL.
Кроме управления доступом к сегментам, привилегии влияют еще и на
выполнение привилегированных команд, выполняемых только при CPL = 0. К
таким командам относятся команды существенно влияющие на ход
вычислительного процесса:
CLI - очистка флага прерываний или запрет прерываний;
STI - установка в " 1" флага прерываний или разрешение прерываний;
CLTS - сброс флага переключенной задачи;
LGDT, LLDT, LIDT - загрузка регистров глобальной, локальной деск-
рипторных таблиц и таблицы дескрипторов прерываний соответственно;
LTR - загрузка регистра задачи;
LNSW - загрузка слова состояний.
Еще к этой группе команд относятся команды пересылки данных MOV,
в которых источником или получателем данных являются регистры управления
CRx, отладки - DRx и контроля - TRx.
Все ранее изученные команды ввода-вывода IN, INS, OUT и OUTS
относятся к классу IOPL-чувствительных, то есть допустимость их выполнения
определяется кодом флагов IOPL в регистре флагов. Если в этом поле записано
lib, то выполнение физических команд ввода вывода возможно на любом
уровне защиты, а если 0, - только при высшем 0-м текущем уровне привилегий
CPL. Изменения уровня привилегий IOPL с помощью команд POPF или POPFD
возможны только при CPL = 0.
Таким образом, система команд и управляющих данных защищенного
режима позволяют построить ядро ОС с произвольной конфигурацией системы
защиты. Диапазон защиты возможен от ее практического отсутствия, если для
всех задач создается CPL = 0, до многоуровневой защищенной системы со
стандартным распределением уровней привилегий. Однако при этом, по
крайней мере, для обращения к подпрограммам ввода-вывода, к которым
обращается прикладная задача, нужно уметь повысить уровень привилегий до 0. В
задаче имеется потенциальная возможность изменения уровня привилегий за счет
использования шлюзов. При переключении задач или вызовах подпрограмм
через шлюзы проверяются следующие правила:
• DPL шлюза должно быть не меньше CPL;
• DPL шлюза должно быть не меньше RPL селектора шлюза;
• DPL шлюза должно быть не меньше DPL целевого сегмента кода;
• DPL целевого сегмента кода должно не больше CPL.
Кроме того, нужно заменить стек, так как уровень привилегий текущего
сегмента стека должен всегда соответствовать уровню привилегий текущего
сегмента кода. При создании сегментов TSS необходимо обеспечить, чтобы
селекторы сегментов кода и стека имели совместимые уровни привилегий.
Изменение сегмента кода может происходить запланированно, когда программа
обращается к шлюзу вызова для временного повышения уровня привилегий. Это
не вызовет особых проблем при низком уровне привилегий стека.
Язык Ассемблера в программировании информационных и управляющих систем 263
Но такая смена может происходить и как следствие возникновения
внешнего или внутреннего прерывания или при переключении задач на уровне
привилегий приходящего процесса, отличающегося от уровня привилегий
прерванной задачи. В этих ситуациях требуется переключение стека задачи, для
чего в TSS предусмотрены указатели стека для уровней 0, 1 и 2, подключающие
свой вариант в зависимости от уровня привилегий прерванной задачи.
В каждом сегменте TSS имеется селектор локальной дескрипторной
таблицы LDT, поэтому при переключении задачи процессор может загрузить в
регистр LDTR селектор новой таблицы LDT. Если дескриптор некоторого
сегмента содержится только в таблице LDT, его не могут использовать никакие
другие задачи, независимо от своих уровней привилегий, которые не работают
с данной таблицей LDT. Следовательно, локальные дескрипторные таблицы
обеспечивают эффективный способ изолирования областей памяти различных
задач. В многопользовательской системе каждая задача может соответствовать
пользователю и в таблице LDT этой задачи можно хранить дескрипторы всех
сегментов памяти конкретного пользователя.
Управление памятью в защищенном многозадачном режиме может
осуществляться в пределах полностью изолированных до полностью совмещенных
адресных пространств. В первом варианте дескрипторы всех сегментов,
требуемых задаче, размещаются в таблице LDT. В таблице GDT могут находиться
только дескрипторы таблицы LDT и сегментов TSS по числу задач. При
наличии задачи супервизора с CPL = 0 в ее таблице LDT должны содержаться
псевдонимы таблиц GDT и IDT, чтобы обеспечить возможность работы с этими
таблицами и возможность отладочного режима. Внутренние структуры данных
или таблица задач защищенного режима должна содержать вместо указателя
верхушки стека селектор соответствующей TSS и для переключения задачи он
должен выполнить соответствующий CALL или JMP.
Простейший способ реализации полностью совмещенных адресных
пространств состоит в использовании GDT для хранения селекторов всех
сегментов. Тогда в селекторе LDT-сегмента должен быть записан 0 и в селекторах
всех сегментов Т1=0. В комбинированных ситуациях наиболее важными
являются ситуации разделения кодов и разделения данных между несколькими
процессами и задачами. Разделяемый сегмент кодов должен иметь достаточно
низкий DPL, чтобы обеспечить обращение от любой задачи и, кроме того, коды
этого сегмента должны быть реентрантными. При работе с разделяемыми
данными могут быть полезными и более сложные комбинации допускающие
раздельное использование частей сегмента.
Особые ситуации, возникающие при нарушении правил передачи
управления обрабатываются точно так, как и другие особые случаи адресации и т.п.,
то есть приводят к появлению прерываний, оговоренных в 6-й главе. Так как
большинство из них является следствием ошибок программистов, то при
обработке чаще всего ограничиваются выдачей информации о состоянии задачи
(посмертной выдачей называемой по английски dump) и прекращением
решения задачи
264 Глава 10 Управление решением задач и организация вычислительных процессов
10.6. Управление информационным обменом
в защищенном режиме
Последнее 16-битовое слово в TSS содержит смещение начала двоичной
карты разрешения ввода-вывода (I/O permission bitmap); она служит
дополнительным средством механизма защиты по привилегиям. Такая карта при
необходимости создается для отдельной задачи и участвует в контроле привилегий
только для команд ввода-вывода (это IOPL-чувствительные команды IN, INS,
OUT, OUTS, CLI и STI).
Без двоичной карты разрешения ввода-вывода программа или задача
может выполнять команды ввода-вывода, если только значение ее CPL меньше
или равно уровню привилегий ввода-вывода IOPL. В двоичной карте
разрешения ввода-вывода каждый бит соответствует одному адресу ввода-вывода
(байтовый порт ввода-вывода). Младший бит первого байта карты относится к
нулевому адресу порта ввода-вывода, следующий бит - к первому адресу и
составляет 64 Кбайта. Полная карта ввода-вывода может занимать 8 Кбайта
памяти Конечно, такая карта требуется не всегда и ее всегда можно завершить в
произвольном месте. Все биты, отсутствующие в усеченной карте считаются
содержащими 1.
Двоичная карта разрешения ввода-вывода размещается в памяти
произвольно, но обязательно вблизи сегмента TSS той задачи, которая пользуется
картой. Смещение в сегменте TSS определяет начало карты относительно
начала самого сегмента TSS. Карта заканчивается либо по достижению 8 Кбайт,
либо по пределу сегмента TSS (тому из них, который меньше).
Как уже отмечалось в главе 6 особое место в программах ввода-вывода
занимает использование прерываний. Главной особенностью по сравнению с
реальным режимам является то, что векторы прерываний заменены таблицей
дескрипторов прерываний IDT, которая определяет 256 обработчиков и может
быть расположена в стороне от векторов реального режима. Механизм
обращения к IDT аналогичен обращению к векторам прерываний в реальном
режиме с тем отличием, что номер вектора прерываний умножается на 8 вместо 4, а
начальный адрес сегмента дескрипторов определяется стандартным для
защищенного режима способом через селектор, загруженный в IDTR. В состав этой
таблицы могут быть включены дескрипторы трех типов: шлюзы задач, шлюзы
прерываний и шлюзы ловушек.
Обращения через шлюзы вызовов, обеспечивают выполнение
отдаленных процедур, в том числе и разделяемых разными задачами, с передачей
параметров, но без возможности создания дополнительных вычислительных
процессов. Шлюзы прерываний и ловушек, созданы для обработки неожиданных
по определению ситуаций прерываний и особых случаев, что исключает
передачу параметров обработчику и позволяет исключить поле счетчика
параметров в дескрипторе. В отличие от реального режима при обращении к ловушке в
случае смены уровней привилегий предварительно фиксируется полный
указатель стека прерванной задачи (8 байтов для SS:ESP), а в некоторых типах
особых случаев после фиксации адреса возврата в стек записывается код ошибки.
Язык Ассемблера в программировании информационных и управляющих систем 265
Шлюз прерывания действует аналогично шлюзу ловушки за одним
исключением, характерным для прерываний реального режима: флаг IF
сбрасывается в 0, сохраняя в стеке старое значение IF. Это предотвращает повторные
обращения к прерыванию, до сброса сигнала прерывания на контроллере
блока приоритетных прерываний. Поскольку реакции на одно и то же прерывание
или особый случай в ВК, как правило, идентичны для всех задач, для
обращения к обработчикам рекомендуется пользоваться глобальной таблицей
дескрипторов, чтобы сократить вероятность ошибки. Кроме того, важно помнить,
что восстановление старого текущего уровня прерываний при выходе из
обработчика возможно только в том случае, когда его CPL = 0.
Обработчики прерываний, подключаемые через шлюзы прерываний и
загрузки можно считать локальными обработчиками, аналогичными
используемым в реальном режиме. Переключение на обработку' прерывания через
шлюз задач изменяет TSS и сохраняет возможность возврата к прерванной
задаче командой IRET за счет установки в регистре флагов NT = 1. Этот подход
более удобен при создании сложных обработчиков, так как он автоматически
сохраняет контекст прерванной задачи и не может ее исказить благодаря
полной изоляции, обеспечиваемой защищенным режимом. Кроме того, такой
обработчик может работать на любом уровне привилегий в заведомо правильной
среде с собственным адресным пространством, определенным в
индивидуальной LDT.
Обработчики прерываний ввода-вывода чаще всего обеспечивают
обмен информацией с внешними устройствами и их функциями являются
формирование и передача буферов в одном из двух направлений. Способы
синхронизации уже рассматривались в главе 6. При построении буферных областей
памяти необходимо в селекторах и дескрипторах обеспечивать доступность как
со стороны обработчика прерываний, который желательно строить для работы
при CPL = IOPL, так и со стороны задачи, которая может быть построена на
любом уровне привилегий. Отметим, что буферы могут быть как системными,
возникающими при обработке открытых файлов, так и пользовательскими,
создаваемыми задачей обмена. С позиций защищенности и надежности
хранения данных желательно изменять права доступа к сегменту DPL при передаче
буферов от задачи к обработчику прерываний и обратно. Для этого
целесообразно воспользоваться псевдонимом или дублирующим дескриптором для тех
частей LDT и GDT, где хранятся дескрипторы буферов обмена.
Краткие итоги
Рассмотренные методы управления задачами позволяют реализовать
многозадачную операционную систему, практически на любом процессоре,
имеющем систему прерываний. Защищенный режим процессоров ix86
позволяет ускорить переключение задач и улучшить производительность сложных ОС.
ГЛАВА 11
ЭФФЕКТИВНОЕ ДИНАМИЧЕСКОЕ УПРАВЛЕНИЕ
РЕШЕНИЕМ ПРИКЛАДНЫХ ЗАДАЧ
В этой /лаве рассматривается применение средств языка
Ассемблера при разработке прикладных npcr/рамм в осш'ктпо-
ориентироваююй среде, основу которой составляет))
обобщенный мно/сюконный /рафпчес кий интерфейс, применяемый в г /к
темах типа Wintlcws
ILL Особенности использования
объектно-ориентированного подхода для решения задач управления
Объектно-ориентированное программирование, получившее широкое
распространение к середине 90-х годов, имело своей базовой целью перейти к
комплексному представлению данных об объекте, воспринимаемом
исследователем или проектировщиком как единое целое. Базовым шагом для реализации
объектно-ориентированного подхода послужили структуры, структурные типы
и объединения в том виде, в котором они были включены в языки С и Pascal.
Структуры и объединения можно считать прототипами статических моделей
объектов предметной области, которые могут восприниматься программами
как единое целое на уровне аргументов и возвращаемых результатов функций и
процедур.
Важным преимуществом применения объектно-ориентированного
подхода стала возможность развития моделей объектов и функций с ними
связанных путем их объединения в комплексы, называемые классами методов,
наделены тремя базовыми свойствами: инкапсуляция (incapculasion), т е.
локализация свойств и характеристик внутри объекта, наследование (inheritance)
свойств базового класса производными классами и полиморфизм
(polymorphism) методов, позволяющий выбирать требуемый вариант
процедуры или функции по спецификации класса и списку аргументов. В состав класса
входят прототипы одиночных объектов, объединяющие по аналогии со
структурами данных внутренние, защищенные и общедоступные данные и процедуры.
Самым ярким примером успехов применения
объектно-ориентированного подхода является построение мощного и наглядного графического
интерфейса работы с прикладными программами и манипуляциями экземплярами
объектов одного и того же класса.
Реализация подобных многооконных систем в системных программах
типа Windows, OS/2 и др. использован так называемый экторный вариант
объектно-ориентированного подхода, название которого происходит от
английского слова action (действие). Основу этого варианта составляет определение
метода как посылки объекту некоторого сообщения, определяющего изменение
Язык Ассемблера в программировании информационных и управляющих систем 267
его состояния. Создание программ, адекватно управляемых сообщениями,
стало необходимым в информационных системах, ориентированных на гибкое
взаимодействие с оператором или пользователем. Для этого были созданы
легко управляемые технологии разнообразных меню, позволяющих выбирать
разнообразные режимы работы. Однако это привело к новым проблемам -
контролю текущего режима работы и контролю действий опасных в смежных
режимах работы.
В основу функциональной стороны системы Windows положены методы
взаимодействия с сигнально-управляемыми графическими объектами,
отображаемыми на экране дисплея, и сигнально-управляющими манипуляторами
типа мыши (mouse). В системах типа Windows поддерживаются графический
интерфейс пользователя (Graphical User Interface-GUI), облегчающий и
стимулирующий написание интерактивных программ и интерактивных оболочек к
логически завершенным объектным модулям. Фактически этот интерфейс вошел
составной частью в стратегию фирмы IBM, получившей название архитектуры
среды для разработки приложений (System Application Architecture - SAA). Эта
стратегия направленна на облегчение переноса программного обеспечения,
подготовленного на одной платформе, в другую рабочую среду, главной
частью которой является обобщенный пользовательский доступ (Common User
Access - CUA). Система MS Windows прошла длительный путь развития через
использование протокола EMS в версиях 2.x к использованию защищенного
режима в версиях 3.x. Дальнейшее развитие MS Windows для построения
обобщенных технологий обработки и управления вычислением в направлении
версий Windows NT и Windows95 обусловлено плохой совместимостью по именам
функций MS Windows и OS/2 из-за различных интерфейсов прикладных
программ (Application Program Interface - API).
В соответствии с правилами или протоколами интерфейсов,
перечисленных системных программ, сообщения представляются 16-битовыми словами
без знака, в котором размещается код сообщения. В современных версиях MS
Windows различают 8 типов сообщений:
• аппаратные, сопровождающие для ввода с клавиатуры или мыши;
• обслуживания окна, запрашивающие операции, информацию о
возможностях и состоянии окна;
• интерфейса пользователя, включающие сообщения системного меню и
меню приложения, а также сообщения, управляющие созданием иерархии
окон;
• сообщения завершения работы программы и системы;
• частные сообщения объектно-ориентированных блоков;
• частные системные сообщения, включающие требования и
информационные сообщения системных ресурсов;
• сообщения динамического обмена данными.
Windows является многозадачной системой, переключающей в
персональных ВК выполнение задач последовательно с одной на другую. При
решении задач используется многопрограммный режим без вытеснения задач,
разрешающий работать другим программам при прерывании их выполнения. Каж-
Глава 11 Эффективное динамическое управление решением прикладных задач
дое нажатие кнопок мыши формирует сообщения, представляющие
возможность выполнения выбранной функции. Таким образом, сообщения можно
рассматривать как управляющие данные или посредники при работе с
трансформируемыми данными. Для вывода результатов и текущего состояния системы
используются средства интерфейса графических устройств (Graphical Device
Interface - GDI), реализующих аппаратно-независимую графику. Этот
интерфейс различает четыре типа устройств, два из которых: экран дисплея и
устройство для получения твердых копий являются физическими, а два другие
битовые образы и метафайлы - виртуальными. Виртуальные устройства
обеспечивают способы хранения изображений на дисках и совместного использования
графических образов прикладными программами.
Основные элементы графического интерфейса составляют:
• блоки диалога и окна с комплексами встроенных подчиненных
элементов;
• меню для выбора режимов и управления работой программ в среде MS
Windows;
• пиктограммы для иллюстративной демонстрации возможностей
программ и выбора режимов;
• курсоры для выбора элементов графических объектов и каретка для
контроля позиционирования в текстах.
В Turbo Ассемблер [53] были включены средства, облегчающие
объектно-ориентированное программирование и позволяющие выполнять его
непосредственно на языке Ассемблера. К этим средствам, прежде всего, относится
возможность описания процедур с аргументами. В заголовке при этом должен
быть указан тип языковых соглашений
имя^рроцедуры PROC тип_языка модификатор__языка тип^рроцедуры
В соответствии с типом языка, определенным либо в директиве .MODEL
(см.гл. 5) либо в заголовке процедуры, Turbo Ассемблер генерирует прологи и
эпилоги процедур, используя еще два типа операторов:
ARG список аргументов
LOCAL список локальных данных
Элементы списков аргументов и локальных данных имеют синтаксис:
имя [размер-1]; имя типа: размер-2
В конце этих списков через знак равенства может быть определено имя
сохраняющее длину любого из списков. Возвращаемые (переменные)
аргументы могут быть выделены дополнительной строкой
RETURNS аргументы
Язык Ассемблера в программировании информационных и управляющих систем 269
При значениях типа языка С, СРР, PROLOG передача параметров
осуществляется в стиле языка С, а в остальных случаях - в стиле языка Pascal. При
таком подходе нет необходимости составлять специальные структурные блоки,
продемонстрированные ранее, что упрощает процесс написания программ.
Если мы хотим сделать все имена, определенные внутри процедур локальными, то
перед первой из них нужно задать директиву LOCALS.
Сохранение регистров в процедуре можно задать директивой
USES список имен регистров
Эти регистры будут сохранены в стеке прологом перед началом
выполнения процедуры и восстановлены в начале эпилога процедуры.
Чтобы приблизить возможности языка Turbo Ассемблер к языкам
высокого уровня в него включены директивы определения прототипов PROCDESC
и процедурных типов PROCTYPE. Для реализации полиморфизма методов
объекта в Turbo Ассемблере пользуются расширенной структурой
Имя^структуры STRUC тип__таблицы имя^предка
METHOD {список^методов^объекта}
где шип_таблицы задается одним из ключевых слов GLOBAL, NEAR, FAR,
после заголовка записываются данные, входящие в состав обычной структуры
объекта, а элемент списка методов занимает отдельную строку и имеет
синтаксис
Тип^метода : тип^результата = имя^метода
Процедуры методов могут вызываться либо по прямому адресу
(статические методы), либо косвенно с помощью указателя таблицы процедур
метода, определенной директивой со следующим синтаксисом в режиме MASM:
Имя_таблицы TABLE список членов таблицы
Здесь элемент списка таблицы задается в той же форме, что и элемент
списка аргументов процедуры.
При создании экземпляра объекта Turbo Ассемблер автоматически
создает указатель на таблицы виртуальных методов, если он не размещен
программистом с помощью директивы TBLPTR. Доступ к таблице осуществляется
с помощью системных имен @ТаЪЫ_имя_объекта для начального элемента
таблицы и @ТаЫег(1(\г_имя_объекта - для ее адреса.
Для работы с виртуальными методами кроме определения таблицы и
создания экземпляра директивой TBLINST необходимо выполнить еще и ее
инициализацию. Последовательность директив
INCLUDE Ist^aso ; Подключение описания
; ассемблерного объекта
2/0 Глава 11 Эффективное динамическое управление решением прикладных задач
DATASEG ; Переключение на сегмент данных
TBLINST ; Резервирование памяти под экземпляр
; объекта может подготовить только память, а для реальной
; работы с виртуальными методами необходимо
; инициализировать таблицу или в конструкторе или при
; инициации экземпляра процедурой метода Lst__Init
Lst__Iint PROC CPP FAR ; Должен быть определен
; как статический
ARG @@MthLst: DWORD
LES BX,@@MthLst ; Загрузка адреса таблицы
; виртуальных методов
TBLINIT ES:DI ; Инициализация данных объекта из аргумента
RET
Lst_Init ENDP
Возможности объектно-ориентированного подхода далеко не
исчерпываются экторным вариантом, ориентированным на посылку сообщений
объекту. Он может использоваться:
• для инкапсуляции частных и общих моделей, моделей не только на
уровне действий, но и на уровне спецификаций;
• как следствие - в перспективе для решения задач автоматизированной
верификации и синтеза программ.
11.2. Динамическая компоновка
Во всех своих режимах функционирования - и на всех этапах своего
развития система Windows пользуется динамической компоновкой, включающей в
себя несколько компонентов [30]. Прежде всего, это средства управления
памятью, обеспечивающие загрузку кода с диска по запросу. Динамическая загрузка
предоставляет возможность освобождения памяти для других нужд путем
сброса из главной памяти объектов*, в оперативном использовании которых нет
необходимости. При работе в реальном режиме процессоров ix86 система
Windows способна обеспечивать динамическую компоновку, которая
функционирует достаточно эффективно, с помощью механизма, полностью
реализованного программным способом. А при работе в защищенном режиме
динамическая компоновка Windows обеспечивается переключением на процесс
перезагрузки встроенными аппаратными средствами управления памятью
центральных процессоров, начиная с i286.
При динамической компоновке реализован способ подключения к
активной программе библиотеки объектных модулей во время ее выполнения.
Это существенно отличается от организации статической компоновки
программой LINK, при которой подпрограммы из библиотеки объектных модулей
копируются в исполняемый файл программы на этапе его создания. Например,
если программа Windows использует подпрограмму malloc из библиотеки
стандартных функций, то копия этой программы при статической компоновке зано-
Язык Ассемблера в программировании информационных и управляющих систем 2 # I
сится в файл результирующей ЕХЕ-программы. Для того, чтобы целевая
программа могла обратиться к обновленной версии подпрограммы malloc (в
случае ее усовершенствования по исполнительным характеристикам или
обобщениям), необходимо заново создавать программный файл. Таким образом,
статически приформировываемая программа не обеспечивает возможность
автоматической модификации при модернизации библиотечных функций. Такую
возможность предоставляет динамическая компоновка, поскольку функции из
динамически подключаемой библиотеки не копируются в исполняемый файл
программы, а подгружаются к ней в процессе ее выполнения. Ценой,
обеспечивающей такую возможность, является увеличение времени выполнения
программы за счет увеличения числа обращений к дискам.
Однако в многозадачной системе эти отношения резко изменяются в том
случае, если разным задачам приходится выполнять сходные действия, для
которых можно использовать одни подпрограммы, используемые уже не только в
качестве ресурсов программирования, а еще и в качестве операционных
ресурсов ВК на шаге решения задач. Это создает дополнительные преим^ riec
связи с тем, что коды и управляющие данные, обслуживающие граф"*';
жимы, а также отображаемые данные весьма громоздки. Другая <;.;< ч
управляющих структур систем отображения связана с многочисленными
модификациями видеосистем и их режимов, поэтому для унификации программного
обеспечения необходимым становится организация динамических связей между
интерфейсными программами и драйверами устройств.
Динамическая компоновка также представляет собой эффективный
механизм для раздельного использования (sharing) кода и данных прикладными
программами. Например, для подпрограммы из графической библиотеки
Windows различными программами системы Windows, которые намерены ею
воспользоваться, совместно применяется единственный экземпляр этой
подпрограммы. Если, например, специализированное текстовое приложение в виде
программы информационного поиска выполняется вместе с программой Word
6.0, то обе они пользуются одной и той же копией подпрограммы отображения
TextOut. В результате можно существенно уменьшить затраты системной
памяти. Примером совместного использования данных может служить управление
их отображением на дисплее. Шрифты GDI заносятся в динамически
подключаемые библиотеки, что создает возможность их совместного использования
или разделения любыми программами, которые необходимы для работы. Даже
если к одному шрифту обращаются несколько программ, в системе достаточно
хранить только одну копию данного шрифта.
Программы, выполняемые в системе Windows, полагаются на
динамическую компоновку для осуществления правильных подключений к различным
библиотечным подпрограммам Windows она. Наиболее важными примерами
динамической компоновки служат обращения из программ Windows к
основным динамически подключаемым библиотекам системы Windows с помощью
файлов ядра KERNEL.EXE (или KRNL286.EXE для устаревшего
стандартного режима, или KRNL386.EXE для усовершенствованного 386 режима),
управления программами пользователя USER.EXE и управления отображением
GDI.EXE. Помимо этого, динамическая компоновка реализуется для трех ос-
сГш cL Глава 11 Эффективное динамическое управление решением прикладных задач
новных динамически подключаемых библиотек Windows применительно к
драйверам устройств Windows. Например, если GDI обращается к
печатающему устройству, то к нему динамически подгружается драйвер печатающего
устройства. Динамическая компоновка обеспечивает обновление или замену
отдельных частей системы Windows, шрифтов, драйверов устройств или даже
основных библиотек.
Механизм динамической компоновки системы Windows основан на
комплексе связующих элементов. Динамическая загрузка программы выполняется
независимо от знаний пользователя о ее работе, однако понимание ее
механизма дает ряд преимуществ пользователю и программисту. Прежде всего, на
уровне управляющих программ снимается проблема недостаточного адресного
пространства на инструментальном компьютере. Кроме того, пользователю
часто требуется обеспечить достаточный уровень эффективности программ,
выбрать оптимальную конфигурацию ВК для решения задач. Одной из
главных проблем здесь является эффективность этого механизма или эффективное
его использование без побочных эффектов. Это необходимо для создания
пользовательских динамически подключаемых библиотек, когда понимание
механизма позволит вам принять решение о целесообразности их применения. С
познавательной точки зрения знание механизма динамической компоновки
является важным для построения аппаратно-программных средств управления
решением задач в сложных В К. Механизм динамической компоновки системы
Windows, это быстрое, высокопроизводительное и очень элегантное решение
проблем управления динамической загрузкой и подключением кодов.
Отправной точкой для построения этого метода являлось создание
механизма для совместного применения библиотечного кода несколькими
процессами. Это достигалось отображением адресного пространства нескольких
процессов в одно и то же физическое адресное пространство. В итоге при
поддержке совместно используемого кода сократились требования к объему
оперативной памяти.
В принципе это динамическая компоновка, но различие состоит в том,
что она была реализована на машине с минимальными возможностями.
Целевая машина для первой версии системы Windows, имела центральный
процессор 8088 фирмы Intel, два дисковода для гибких дисков и ОЗУ 256 Кбайт. Такая
динамическая компоновка требовала минимальных накладных расходов и
должна была осуществляться только программными средствами, хотя на всех
других машинах она была реализована с расширением возможностей
аппаратных средств.
В условиях минимального объема памяти было решено разместить как
можно большую часть системы в сегментах кодов, которые при отсутствии
потребности можно было бы сбрасывать на диск, что оставляло больше места в
памяти для нужд прикладных программ. Здесь главным было создать механизм
занесения сегмента кода в оперативную память, для чего требовался целый
набор новых инструментальных средств.
Для этого понадобилось создать новые компоновщики и загрузчики,
которые обслуживали бы новый файловый формат .ЕХЕ, необходимый для
поддержки динамической компоновки. И наконец, необходимо было модифициро-
Язык Ассемблера в программировании информационных и управляющих систем 273
вать компилятор так, чтобы он генерировал код, соединяющий вместе
программы и динамически подключаемые библиотеки, имеющие расширение .DLL
(Dynamic Link Libraries) в единый фрагмент работоспособной системы
Главное, что было выделено в этой задаче - это проблема определения
происхождения ошибочной ситуации от инструментальных средств или от
пользовательских программ.
Программный загрузчик первой версии системы Windows, выполнил
роль фундамента, на котором были построены все последующие загрузчики.
Для описания их принципов работы необходимо изложить, как динамически
загружаются сегменты кодов и подгружаются подпрограммы для
сбрасываемых, перемещаемых и постоянно распределенных сегментов. Остановимся на
наиболее сложном случае - сбрасываемом коде. Построение механизма
динамической компоновки, подобной используемой в системе Windows, самой
главной проблемой при работе со сбрасываемыми сегментами является
информация об их фактическом размещении. В отсутствие аппаратных средств для
работы с памятью в защищенном режиме система Windows в реальном режиме
строит крошечный кусочек кода для каждой процедуры типа FAR
динамически подключаемой библиотеки. Эти фрагменты кода называют передачей
загрузки или передачей обращения и располагаются в базе данных модуля.
Естественный путь динамического связывания состоит в том, чтобы
использовать на шаге выполнения последнюю информацию об именах типа
PUBLIC и EXTERN в модулях. Для нормального функционирования
информация о них должна сохраняться весь период работы задачи в системе, для чего
создается встроенная база данных.
Предположим, что разрабатывается графическая программа, которая
пользуется подпрограммой Rectangle из GDI Для данного примера допускаем,
что эта программа, названная DISPRECT, размещена в удаленном сегменте
данных. Перед программой система Windows создаст базу данных модуля GDI.
Внутри базы данных модуля GDI для каждой подпрограммы типа FAR
существует одна передача обращения к функциям Rectangle. В реальном режиме
передача обращения для программ из сегментов, сброшенных на диск, принимает
одно из двух состояний: прерывание с переходом на загрузчик или переход на
программу, размещенную в памяти
Когда начинает работать первый запрос DISPRCT, загрузчик Windows
выполняет процесс загрузки, создавая внутрисистемную базу данных модуля
DISPRCT Э~а база данных модуля необходима для определения
местонахождения его точки входа для начальной передачи управления на эту точку входа.
Динамическая ссылка от DISPICT к подпрограмме Rectangle GDI
возникает тогда, когда система Windows загрузит тот сегмент кода, который
действительно обращается к Rectangle К концу этого сегмента кода
присоединяется таблица настройки, содержащая информацию об адресах, необходимых для
выполнения первой части динамической компоновки.
Внешние ссылки определяют обращения к таким подпрограммам
динамически подключаемой библиотеки, как Rectangle из GDI. Когда в память
загружается сегмент кода DISPICT, вызов типа FAR подпрограммы Rectangle
настраивается на адрес псевдонима подпрограммы Rectangle, те на передач)/
10 В И Пустоваров
274 Глава 11 Эффективное динамическое управление решением прикладных задач
обращения, в базе данных модуля GDI. Когда загрузчик Windows завершает
подготовку всех адресов, описанных в таблице настройки сегмента, он
освобождает относящуюся к этой таблице оперативную память. Если настроечная
информация понадобится вновь, то она будет еще раз считана с диска вместе с
сегментом кода.
После загрузки кода сегмента в память, один раз для каждого
обращения типа FAR как к внутренним, так и к внешним подпрограммам, в сегменте
ставится "заплата". Это означает, что код в заново загружаемом сегменте кода
модифицируется таким образом, что каждый вызов типа FAR ссылается на
некоторый исполняемый код. Для пользовательских программ системы Windows
может оказаться полезным сократить количество сегментов кодов и свести до
минимума число вызовов типа FAR, поскольку при загрузке кодового сегмента в
память возникают накладные расходы.
Возвращаясь к нашей программе-примеру, отметим такой интересный
аспект динамической компоновки: даже если код сегмента DISPRECT для
обращения был настроен на базу данных модуля GDI, код сегмента, который
действительно содержит подпрограмму Rectangle, в памяти отсутствует.
Следующий шаг в процессе динамической компоновки происходит, когда DISPPICT
фактически вызывает Rectangle передачей обращения через базу данных
модуля. Как мы уже упоминали выше, передача обращения - это фрагмент кода,
который имеет одно из двух состояний в зависимости от готовности к работе
Rectangle. Давайте предположим, что она к работе не готова, чтобы увидеть,
как будет реагировать на это передача обращения. В таких случаях передача
обращения будет иметь вид кода, похожий на приведенный ниже:
; —» " К работе не готова"
SAR CS:[00B4h],1 ; Изменить флажок доступа
INT 3Fh ; Вызвать загрузчик Windows
DB seg ; Номер сегмента кода
DW off ; Смещение для подпрограммы Rectangle
Если DISPRECT обращается к этому фрагменту кода, то он вызывает
программное прерывание 3Fh, которое обращается к загрузчику Windows.
Загрузчик системы Windows считывает следующие 3 байта данных, которые
содержат номер сегмента (от 1 до 255) и смещение внутри сегмента кода (от 0 до
65535). Поскольку номер сегмента представляется одним байтом, то общее
число сегментов, допустимое в любом файле типа .ЕХЕ или .DLL составляет 255
(нулевой сегмент зарезервирован для передачи возврата управления). Данное
ограничение свойственно формату самого файла типа .ЕХЕ с более чем 255
сегментами. Тем не менее, можно построить динамически подключаемую
библиотеку, которая позволяла бы программе легко обходить это ограничение.
Как только загрузчик Windows считал необходимый сегмент кода с
диска, он изменяет базу данных таким образом, чтобы передача, относящаяся к
подпрограмме Rectangle, содержала машинные команды, представляемые
приведенным ниже фрагментом на Ассемблере:
; —» "К работе готова"
Язык Ассемблера в программировании информационных и управляющих систем 275
SAR CS:[00B4h],l ; Изменить флажок доступа
JMP Rectangle
Но настройка адресов обеспечивает не только передачу обращений к
подпрограмме Rectangle, но и для всех передач всем другим подпрограммам
типа FAR, находящихся в сегменте кода Rectangle. И если Rectangle выполняет
вызовы типа FAR к другим сегментам кодов, то эти обращения настраиваются
наряду со всеми другими обращениями типа FAR из сегмента кода Rectangle
Таким образом, корректируются вызовы типа FAR, направленные как внутрь,
так и наружу модуля. Когда сегмент кода загружен в память и настроена база
данных модуля, динамическая компоновка завершается. Подпрограмма
Rectangle сможет получить из стека параметры и выполнить свое
функциональное назначение.
Все обращения к Rectangle настраиваются на псевдоним обращения к
Rectangle в базе данных модуля, который для этой подпрограммы играет роль
как бы оператора переключения. Обратите внимание на то, что программа
управления памятью системы Windows может в любой момент переместить
сегмент с Rectangle. Если подобное происходит, то настраивается адрес передачи
обращения к Rectangle (и передачу обращений к другим подпрограммам типа
FAR, находящимся в том же сегменте). Помимо этого, сегмент кода может
быть в любой момент удален из памяти. После этого динамический
компоновщик просто настраивает адрес передачи обращения с помощью команды INT
3Fh, чтобы передать управление загрузчику, который должен восстановить в
памяти сегмент кода. Механизм динамической компоновки позволяет удалять
из памяти сегмент кода и загружать его позднее, чтобы получить большую
гибкость в использовании оперативной памяти.
Важно отметить, что встроенные базы данных находят широкое
применение и в разнообразных системах автоматизации программирования для
разнообразных целей, например, для управлением выборкой семантически
связанных понятий в подсистемах помощи и подсказках. В сочетании с решением
целевых задач управляющих программ это позволит поднять системы
автоматизации программирования в ближайшее время на новый уровень.
11.3. Эффективная компоновка и выполнение модулей
Основной прием эффективной динамической компоновки опирается на
постоянно распределенные сегменты кодов [30]. Из описания динамической
перезагрузки ясно, что выполнение динамической компоновки для постоянно
распределенных сегментов кодов еще проще. Динамические связи к постоянно
распределенным сегментам кодов реализуются не через передачу обращения, а
связью по прямому адресу вызывающей и вызываемой подпрограмм. Это
обусловлено тем, что постоянно распределенные сегменты кодов всегда
интерпретируются как предварительно загруженные сегменты, поэтому они всегда
располагаются в оперативной памяти и никогда не перемещаются.
Рассмотрим прикладную программу, вызывающую подпрограмму
TextOut из GDI, которая в момент вызова находится в постоянно распределен-
276 Глава 11 Эффективное динамическое управление решением прикладных задач
ном сегменте кода. Назовем нашу программу, которая обращается к TextOut,
DISPTEXT. При загрузке GDI, как всегда, создается база данных модуля GDI.
После этого загрузчик считывает все постоянно распределенные сегменты
кодов в память.
Поскольку постоянно распределенный сегмент кода загружается в
начале работы GDI, то он никогда не передает управление загрузчику. Чтобы
определить местонахождение самой программы, в базе данных формируется список
адресов подпрограмм типа FAR. Эта информация необходима загрузчику при
загрузке в оперативную память DISPTEXT для усранения повторных копий. В
это время осуществляется динамическая компоновка, с помощью которой
DISPTEXT напрямую связывается с TextOut. В отличие от настройки адресов в
случае перемещаемого или сбрасываемого сегмента, при настройке адреса для
постоянно распределенного сегмента кода управление всегда передается прямо
от вызывающей программы вызываемой подпрограмме.
Аппаратные средства управления памятью могут перемещать модели
объектов без изменения их логического адреса в памяти. Поэтому все
настраиваемые адреса при динамической компоновке интерпретируются так же, как и
при ранее рассмотренной динамической загрузке. Другими словами, все
настраиваемые адреса в защищенном режиме рассматриваются точно так, как и
для фиксированных сегментов кода. Для прямого присоединения вызывающей
программы к вызываемой подпрограмме, адреса вызовов типа FAR
настраиваются загрузчиком Windows как для перемещаемых, так и для сбрасываемых
сегментов.
Поскольку в защищенном режиме в базе данных модуля отсутствуют
передачи обращения, то нужно подробнее остановиться на процессе загрузки
сбрасываемых сегментов кодов. В защищенном режиме при обращении к
отсутствующему коду возникнет ошибка доступа к сегменту. Как было представлено
в главе 10 в защищенном режиме, селектор каждого сегмента содержит индекс
для таблицы дескрипторов защищенного режима. Помимо прочей
информации, хранимой в дескрипторах, в них располагается флажок, позволяющий
системе определить, действительно ли сегмент располагается в оперативной
памяти. Если он отсутствует, то возникает ошибка обращения к сегменту, что
вызывает прерывание по особому случаю, извещающее программу управления
памятью о необходимости подгрузки отсутствующего сегмента. Программа
управления памятью системы Windows загружает в память сегмент кода и
затем повторяет команду, обратившуюся к сегменту. Таким образом, аппаратные
средства управления памятью работают в защищенном режиме незаметно для
прикладных программ.
Программы динамической компоновки защищенного режима весьма
просты за счет возможностей аппаратных средств управления памятью,
обеспечиваемых различными процессорами фирмы Intel. Сегменты могут
перемещаться и загружаться в физической памяти без изменения логических адресов,
которыми пользуются программы. Перезагрузка сбрасываемого сегмента
выполняется автоматически при возникновении прерывания "сегмент отсутствует".
В реальном режиме система Windows использует ряд приемов, чтобы
достигнуть такой же гибкости, обеспечиваемой в защищенном режиме аппарат-
Язык Ассемблера в программировании информационных и управляющих систем 277
но. Главными из этих приемов являются установка "заплаты" на стек и
использование передачи возврата. Остановимся на этих механизмах более подробно.
Установка "заплаты" на стек включает в себя просмотр стека
программы и изменение ссылок, которые были сделаны на перемещенные сегменты
кодов. Обратим внимание на то, что после перемещения сегмента кода
необходимо изменить ссылки не только на входные точки подпрограммы в базе данных
модуля, но и адреса возвратов в переместимые модули.
Независимо от способа вызова функции, из динамически подключаемой
библиотеки, либо из обычной программы, адрес возврата заносится в стек.
Этот адрес определяет расположение машинной команды, выполняемой
следующей при выходе из подпрограммы. Если сегмент кода содержит ссылку на
перемещение сегмента, Windows должна просмотреть стек и поставить
"заплату" на адрес возврата таким образом, чтобы после обращения можно было
правильно вернуть управление в переместившуюся вызывающую функцию.
Windows ставит "заплаты" на стек для каждого экземпляра любой программы,
выполняющейся в системе. Возможность Windows ставить на стек "заплаты"
обеспечивает свободную переместимость любого сегмента кода практически в
любой момент времени, что позволяет при работе Windows в реальном режиме
добиваться такой же гибкости, как и в защищенном режиме.
Если установка "заплат" на стек позволяет перемещать сегменты кодов,
передачи возврата управления предоставляют возможность их сбрасывать из
главной памяти. При реализации этих механизмов необходимо вносить
изменения в специальную структуру управления стеком и базу данных модуля,
которые вызывали бы загрузчик Wmdows в тот момент, когда в сегменте происходит
обращение к функции.
Когда Windows сбрасывает сегмент кода, на который в стеке есть ссылка
как на адрес возврата, на стек также ставится "заплата", возвращающая
управление на передачу возврата. Передача возврата похожа на передачу
обращения, за исключением того, что после загрузки необходимого сегмента кода она
выполняет роль мостика для команды возврата управления, а не для команды
обращения. После того как сегмент загружен, выполнение продолжается с
адреса возврата, который находится в стеке перед удалением сегмента. Ниже
приводится пример передачи возврата управления:
INT 3Fh ; Вызвать загрузчик Windows
DB 0 ; Нулевой сегмент - это признак передача возврата
DB ? ; Информация о сегменте кодов
DB ? ; Информация о сегменте данных
Передача возврата управления сходна с передачей управления
загрузчику, за исключением задания нулевого недопустимого номера сегмента, что
является признаком передачи возврата для загрузчика INT 3F. В данном случае
флажок знает, что в последующих трех байтах содержатся все данные, которые
ему необходимо передавать, хотя упаковываются они и более плотно. Селектор
сегмента данных хранится в передаче возврата управления, поскольку сам
сегмент данных можно переместить, если не будет хватать оперативной памяти.
Когда выполняется передача возврата, то, чтобы отобразить новое расположе-
278 Глава 11 Эффективное динамическое управление решением прикладных задач
ние сегмента данных, ставится "заплата" на сегмент данных. Все это позволяет
системе Windows сохранять работоспособность даже в условиях острой
нехватки оперативной памяти, потому что любой сегмент кода в произвольной части
системы (за исключением выполняющегося в данный момент) можно удалить,
если возникнет потребность в дополнительной памяти.
Теперь рассмотрим далее воздействие динамической компоновки на
данные. В системе Windows каждой программе и динамически подключаемой
библиотеке разрешается создавать используемый по умолчанию сегмент
данных, при этом обеспечиваются несколько механизмов, позволяющих быть
уверенными, что каждая программа и динамически подключаемая библиотека
всегда в состоянии получить доступ к своим используемым по умолчанию
данным.
При составлении системно-независимых прикладных программ можно
пользоваться функциями из различных статически подключаемых библиотек.
При этом подобное объединение незаметно в программе, поскольку
практически невозможно отличить машинные коды приложений от машинных кодов
библиотечных подпрограмм. Для сохранения глобальных данных о состоянии
программы, компоновщик смешивает библиотечные глобальные переменные с
глобальными переменными приложения. При работе с динамически
подключенными библиотеками различие между приложением и библиотечным кодом
более очевидно. Например, динамически подключаемые библиотеки имеют
собственные отдельные исполняемые файлы; GDI.EXE - это файл динамически
подключаемой библиотеки, в котором находятся подпрограммы GDI. Файл в
данном случае является самостоятельным объектом, который существует
независимо от программ, пользующихся библиотечными подпрограммами.
Динамически подключаемые библиотеки также имеют собственный
отдельный сегмент данных для хранения глобальных переменных отдельно от
областей данных программ, пользующихся библиотечными подпрограммами.
Служебные программы для просмотра памяти, например, Heap Walk из пакета
SDK фирмы Microsoft предоставлют вам возможность обнаружить сегмент
данных динамически подключаемой библиотеки. Программа Heap Walk
отмечает большей яркостью сегменты данных для программ USER WINOLDAP и
DLL (WINOLDAP обеспечивает поддержку для прикладных программ DOS,
если они выполняются под управлением системы Windows).
Системная библиотека Windows делает некоторые функции доступными
для приложений Windows. Эти функции присоединяют к приложениям
возможности и средства динамически подключаемых библиотек. Такие функции
специально маркируются как экспортируемые, поэтому система Windows
способствует получению динамически подключаемой библиотекой доступа к
соответствующим сегментам данных. Для подключения библиотечного сегмента
данных Windows модифицирует начальные байты каждой экспортируемой
библиотечной функции, используя фрагмент кода, подобный данному:
MOV AX,DGROUP; Загрузить селектор сегмента данных
PUSH DS ; Сохранить DS вызывающей программы
MOV DS,AX ; Настроить регистр DS на сегмент библиотеки
Язык Ассемблера в программировании информационных и управляющих систем 2Т9
Когда библиотечный сегмент данных перемещается, то система Windows
для всех экспортируемых функций из данной библиотеки модифицирует
значение сегментного адреса DGROUP. Это приводит к тому, что регистр сегмента
данных соответствующим образом настраивается к любому моменту
обращения программы к экспортируемой функции-мостику. Полный набор команд на
Ассемблере, предназначенный для настройки сегмента данных экспортируемой
функции типа FAR из динамически подключаемой библиотеки, имеет вид:
MOV AX, DGROUP
INC BP
PUSH ВР
MOV ВР, SP
PUSH DS
Загрузить селектор сегмента данных
Сформировать нечетный адрес
Сохранить смещение области параметров
Подготовить смещение новой области
параметров
Сохранить DS вызывающей программы
MOV DS, АХ ; Настроить регистр DS на сегмент библиотеки
Ранее, при рассмотрении некоторых приемов, к которым вынуждена
прибегать в реальном режиме система Windows, мы отметили, что иногда она
должна просматривать стек и ставить на нем "заплаты" при перемещении либо
сбросе из памяти сегмента кода. С помощью команд PUSH ВР и MOV BP,SP
виртуальная машина обычно запоминает старое значение ВР и настраивает его
для личного пользования локальных данных в стеке текущей функцией. При
этом быстро создается связанный список записей активации, который
облегчает выполнение просмотра стека. Команда INC ВР создает нечетный адрес,
использующийся только при обращениях типа FAR и таким образом
предоставляет при просмотре стека возможность различать вызовы типа NEAR и FAR
по содержимому стека
В определенные моменты библиотечная подпрограмма Windows буде1
обращаться к внутренним функциям системы Windows. Подобные
подпрограммы обращаются как бы к функциям обратного вызова. К двум ранее
прагматически очевидным типам функций обратного вызова - оконным процедурам и
процедурам диалоговых блоков нужно добавить процедуры перечисления и
процедуры подклассов. В динамически подключаемой библиотеке для
перенастройки регистра сегмента данных в случае функции обратного вызова
необходимо предпринять специальные действия. Это можно осуществить с помощью
списка экспортируемых функций в файле определения модуля программы (типа
.DEF). Ниже, например, показано, как можно экспортировать оконную
процедуру в программу средствами языка С:
EXPORTS
MinWindowProc
либо можно воспользоваться директивой компилятора „exports:
LONG FAR PASCAL _exports MinWindowProc
(HWND hwnd, WORD wMsg, WORD wParam, LONG lParam)
280 Глава 11 Эффективное динамическое управление решением прикладных задач
Третий способ, который применим при работе с компилятором Turbo
C++, - интеллектуальный экспорт, обеспечивающий выигрыш за счет того, что
в момент переключения задач регистр сегмента стека (SS) содержит правильное
значение сегмента данной задачи. Поскольку стек прикладной программы
практически всегда располагается в используемом по умолчанию сегменте
данных прикладной программы, можно скопировать значение регистра SS в
регистр DS. Такое выполнение для каждого обращения типа FAR означает, что
вам никогда не нужно беспокоиться об экспорте функции, либо прибегать к
подпрограмме MakeProcInstance. Такой технический прием был применен при
создании для Windows-версии программы управления для системы управления
базами данных Windows SQL Gupta Technology. Код начального фрагмента
пролога процедуры типа far, обеспечивающего интеллектуальный экспорт
имеет вид:
MOV AX, SS
INC BP
PUSH ВР
MOV ВР, SP
PUSH DS
MOV DS, AX
Скопировать стек в АХ
Сформировать нечетный адрес
Сохранить смещение области параметров
Подготовить смещение новой области
параметров
Сохранить DS вызывающей программы
Восстановить DS
Но при этом нужно убедиться в том, что функции обратного вызова
программы действительно экспортированы Когда Вы не пользуетесь
интеллектуальным экспортом, то в начало каждой функции типа FAR компилятор
помещает следующий фрагмент
PUSH DS
POP AX
NOP
INC BP
PUSH BP
MOV BP, SP
PUSH DS
MOV DS, AX
Записать значение DS в
регистр АХ
Установить резервный байт кода
Сформировать нечетный адрес
Сохранить смещение области параметров
Подготовить смещение новой области
параметров
Сохранить DS вызывающей программы
Настроить ваш собственный DS
На первый взгляд кажется, что данный фрагмент выполняет
бесполезную задачу. Но в действительности jtot несколько усложненный кусок кода
гарантирует, что каждое обращение типа FAR запоминает копию регистра
сегмента данных вызывающей программы в стеке. Это предоставляет системе
Windows возможность настроить адреса сегментов данных, которые
перемещаются одновременно с настройкой адреса сегментов кодов. В произвольное
время при перемещении любого кода или адреса сегмента для Windows не
составляет проблем настроить его соответствующим образом.
Язык Ассемблера в программировании информационных и управляющих систем 281
Когда сегмент кода загружается в память, пролог экспортируемой
функции настраивается путем замены трех начальных команд командами NOP (нет
операции):
Сформировать нечетный адрес
Сохранить смещение области параметров
Подготовить смещение новой области
параметров
Сохранить DS вызывающей программы
Настроить ваш собственный DS. Это позволяет
; экспортируемой функции получать значение ее сегмента
; данных в регистре АХ и зависит от типа функции обратного
; вызова: процедуры ;системы Windows используют один
; механизм, а остальные функции - другой.
Таблица 11.1
NOP
NOP
NOP
INC
PUSH
MOV
PUSH
MOV
BP
BP
BP, SP
DS
DS, AX
Функция обратного
вызова
Процедура
диалогового блока
Процедура
перечисления
Ловушка
Процедура
оповещения о
сбрасывании памяти
Процедура
подкласса
Таймер
Описание
Используется для настройки и обслуживания
диалогового блока
Для информирования системы Windows о типе объекта,
программа пользуется процедурой перечисления. Для
каждого объекта система Windows один раз вызывает
процедуру перечисления. К перечисляемым объектам
относятся окна, шрифты, объекты рисования GDI,
форматы буфера Clipboard
Позволяет программе просматривать и изменять
маршрут сообщений в сисюме Ловушка клавиатуры,
например, дает про1рамме возможность среагировать на
любую "горячую11 клавишу даже в том случае, если
программа в данный момент не была активна
Функция GlobalNotify позволяет программе настраивать
процедуру обратного вызова, обращение к которой
выполняется перед тем, как программа управления
памятью системы Windows удалит обьект из памяти
Обеспечивает средства для просмотра и модификации
маршрута сообщений для определенного окна
Процедура таймера предоставляет возможность
определить другой способ получения сигналов таймера,
отличный от реализуемого сообщением WM_TIMER
Оконная функция получает значение ее регистра сегмента данных как
часть механизма доставки сообщений Windows Когда при создании окна вы-
282 Глава 11 Эффективное динамическое управление решением прикладных задач
полняется CreateWindow (но не к CreateWindowEx), ей передается дескриптор
экземпляра, определяющий сегмент данных, связанный с окном. Механизм
доставки сообщений пользуется этим значением для настройки значения АХ в
случае оконных процедур.
Все другие функции обратного вызова пользуются другим механизмом,
требующим дополнительной работы, но гарантирующим, что регистр АХ
будет содержать правильный адрес сегмента данных. Этот механизм назван
передачей экземпляра.
Механизм динамической компоновки позволяет эффективно по
затратам памяти связывать во время выполнения программы коды из различных
модулей исполняемых программ и динамически подключаемых библиотек.
Каждый модуль может иметь свой собственный сегмент данных, для работы с
глобальными переменными. Настройка сегмента данных (регистра DS)
выполняется всегда при пересечении границы модуля. Кроме способов его формирования
для динамически подключаемых библиотек и оконных процедур в прикладных
программах имеется третий способ, реализуемый под управлением программ
системы Windows.
В таблице 11.1 приведен перечень различных процедур обратного
вызова, которые могут быть созданы в системе Windows. Обратный вызов служит в
Windows для различных целей, а его механизм эффективно реализует для
системы Windows доставку информации в программу, выполняемую под Windows
Передача экземпляра необходима только в случае, когда процедура
располагается в программе системы Windows. При обратном вызове через
динамически подключаемые библиотеки в интеллектуальном экспорте передача
экземпляра не нужна. Она выполняется маленьким фрагментом кода, например.
MOV AX, DSvalue
JMP DialogBoxProc
Если подпрограмма DialogBoxProc находится в постоянно
распределенном сегменте кода, то эта подстройка будет передавать управление
непосредственно на код.
Остановимся на функционировании реального режима, в котором
обращения к перемещаемым и сбрасываемым сегментам кодов всегда происходят
путем передачи обращения через базы данных модулей. При добавлении
передачи экземпляра управление осуществляется так: сначала для записи значения
сегмента в регистр АХ выполняется передача экземпляра, а затем для загрузки
самого сегмента выполняется передача обращения, либо выполняется переход
на сегмент, если такой существует в памяти.
Нужно обратить внимание на то, что обращение осуществляется как
через базу данных задачи, так и через базы данных отдельных модулей
программы. В базе данных задачи передача экземпляра записывает значение сегмента
данных программы в регистр АХ. При перемещении сегмента данных
программа управления памятью просматривает все активные передачи в базе данных
задачи, чтобы удостовериться, что они остаются текущими. После базы данных
задачи просматривается база данных модуля, что обеспечивает динамическую
Язык Ассемблера в программировании информационных и управляющих систем 283
загрузку, рассмотренную раньше. Этот механизм предоставляет возможность
сбрасывать или перемещать сегмент кода в реальном режиме.
На последнем шаге управление попадает на процедуру обратного
вызова, в которой одной из первых команд в процедуре устанавливается адрес
сегмента данных копированием значения из регистра АХ.
В программе передача экземпляра происходит при вызове
подпрограммы MakeProcInstance. В качестве одного из параметров этой подпрограмме
передается адрес процедуры, который в реальном режиме является адресом
передачи обращения на базы данных модулей для функций, располагающихся в
сбрасываемых или перемещаемых сегментах. Возвращает она указатель на
передачу экземпляра, который может потом применяться в качестве адреса
процедуры типа FAR для тех библиотечных подпрограмм системы Windows,
которым необходим адрес процедуры обратного вызова.
Три рассмотренные типа элементарных объектов, реализованные в
функциях типа FAR - экспортируемые библиотечные разделы, неэкспортируе-
мые и экспортируемые программные элементы имеют важное значение для
реализации базовых механизмов системы Windows. Первый из них обеспечивае1
настройку загрузчиком системы Windows сегмента данных динамически
подключаемой библиотеки. Второй - создается компилятором С, чтобы
предоставить возможность правильно работать другим элементам. А третий
предназначен для функций обратного вызова типа оконных процедур и процедур
диалоговых блоков.
Эти три элемента созданы для функций типа FAR, которые в Windows
восстанавливают стек одним и тем же способом с помощью следующих
команд:
MOV SP,BP ; Восстановить локальную область стека
вызывающей программы
Восстановить DS вызывающей программы
Сбросить список связи для просмотра стека
Восстановить признак обращения типа FAR
Возврат типа FAR
Кроме того, восстанавливается значение сегмента данных вызывающей
программы в регистре DS Пока восстанавливаемое содержимое этого регистра
находится в стеке, программа управления памятью может его изменить, так
чтобы он соответствовал новому расположению сегмента данных. Список
связи для просмотра стека стирается командой POP BP, при этом уменьшается
регистр ВР, принятый в качестве признака, различающего при просмотре стека
обращения типа FAR от вызовов типа near. И последнее, командой возврата
управление передается в вызывающую программу либо на передачу возврата,
если в реальном режиме сегмент кода пришлось сбросить.
Механизм динамической компоновки является мощным и гибким
средством, позволившим ранним версиям системы Windows работать в реальном
режиме с приемлемой производительностью. Он по-прежнему применяется в
современных версиях Windows как в реальном, так и в защищенном режимах
POP
POP
DEC
RETF
DS
BP
BP
0002
284 Глава 11 Эффективное динамическое управление решением прикладных задач
работы. Более того, динамическая компоновка является основной
архитектурной компонентой операционных систем OS/2 и Windows NT.
Динамическая компоновка затрагивает как код, так и данные. Что
касается кода, то при этом программа получает возможность подключать
библиотечный код во время выполнения, а не на этапе компоновки. Это обеспечивает
беспроблемный перенос в версии 3.x и выше Windows программ, написанных
для ранних версий системы и облегчит запуск программ, без внесения
изменений в будущих версиях системы Windows. Что касается данных, то
применительно к ним динамическая компоновка почти так же прозрачна, как и для
программ. Перед тем как сделать их интеллектуально экспортируемыми,
программисты для системы Windows должны экспортировать свои процедуры
обратного вызова и настроить их с помощью MakeProcInstance. Интеллектуальный
экспорт помогает сделать упростить динамическую компоновку
применительно к данным.
Краткие итоги
Рассмотренные ассемлерные вставки программы Windows демонстрирут
возможности гибкой реализации программ компоновщиков и загрузчиков. Рас-
смотреные механизмы удобны для разработки программ
объектно-ориентированного стиля средствами Turbo Ассемблера.
ГЛАВА 12
ОТЛАДКА И ТЕСТИРОВАНИЕ ПРОГРАММ НА
УРОВНЕ МАШИННЫХ КОМАНД
В мюч раздел? рассмотрены важные схобеннсхти от.чадки
про/рачм на уровне машинных кодов, методика отладки и ана
лиза особых ситуаций, возникающих на уровне машпнныл
команд Эта метсх)кка чожет быть положена в основу работ по
контролю правильности работы щкщтмм при их
тестировании и стытноп эксплуатации, а также при отладке вновь
разрабатываемо?^ аппаратуры внешних ус т]юпств.
12.1. Задача проверки правильности программ
и ее типовые решения
С появлением практического программирования весьма актуальным
стал вопрос обеспечения правильной работы программ. Программы, как
отмечалось в главе 1, чаще всего являются следствием эквивалентных
преобразований аналитических моделей в последовательность действий. Поэтому для
контроля их правильности возможны действия по проверке формализмов путем
доказательства корректности через обратные преобразования и проверку
контрольных отношений. Однако подобная методика полагается на доверительное
соглашение о корректности и адекватности аналитических моделей реальным и
проектируемым объектам, что далеко не всегда соответствует существующим
реалиям.
Другая сторона контроля правильности программ, связанная с
фактической неточностью моделей и их адекватностью, ограниченной чаще всего узким
диапазоном пространства определения, состоит в определении диапазонов
допустимых значений исходных данных и результатов вычислений. Таким образом,
для получения требуемой корректности программ можно еще на этапе их
проектирования учитывать диапазоны допустимых значений входных переменных
решаемой задачи, а также фактических параметров используемых стандартных
программ и положенных в основу их реализации аналитических моделей.
Проверку программ на предмет аналитического доказательства их
правильности принято называть верификацией. Верификация может выполняться
любым методом, принятым в элементарной или высшей математике. При этом
нужно помнить, что для большего эффекта действий по выявлению ошибки в
программе, желательно пользоваться альтернативным путем математического
доказательства по отношению к тому, который использован в алгоритме или
программе для получения вычислительных выражений. Повторение таких
преобразований одним и тем же специалистом при анализе той же задачи может
привести к повторению тех же заблуждений и промахов в доказательстве, кото-
286 Глава 12 Отладка и тестирование программ на уровне машинных команд
рые были допущены в программе. Поэтому для верификации правильности
программ, как и в элементарной математике, могут выполняться обратные
преобразования и подстановки. Но при верификации нельзя преодолеть гипотезу
корректности первичной модели, то есть нельзя выявить ошибки в этой модели.
При верификации для устранения этого недостатка необходимо
значительно расширять модели предметной области и ее пространства базовыми
аксиомами, позволяющими определить фундаментальные ограничения и
отношения, такие как например, разнообразные законы сохранения. Однако контроль
даже относительно простых ограничивающих аксиом во много раз усложняет
работу аналитика над моделью, которая до сих пор не имеет серьезных средств
программной поддержки. Еще более сложным является автоматизация
доказательства правильности программы по спецификациям ее целей и формализмам
предметной области. Результаты научных исследований в этой области пока не
нашли существенного отражения в программных продуктах массового
пользования.
Автоматизация доказательств традиционно относится к классу задач
искусственного интеллекта и в качестве исходных данных требует в связанной
форме спецификаций результатов программ, их формализмов, а также
математических формализмов предметной области на соответствующих языках. Эти
языки развиваются более двух десятков лет, однако до сих пор не стали
предметом широкого коммерческого распространения Проблемы
неавтоматизированной верификации состоят в том, что аналитическое доказательство
правильности требует поиска альтернативных путей аналитических
преобразований, чтобы серьезно гарантировать возможность корректного подтверждения
правильности программ, особенно, если составление программы и верификация
выполняются одним и тем же специалистом.
В большинстве информационных и управляющих систем на
определенных этапах вычислений или при восприятии результатов происходит
сопоставление данных о реальных объектах и о их моделях. Это позволяет перейти от
проверок на уровне аналитических абстракций к проверкам на уровне
фактически обрабатываемых входных данных и полученных в результате обработки
выходных данных. Такой подход к проверке правильности программ принято
называть тестированием. Тестирование необходимо выполнять перед
контрольными испытаниями вновь разработанных программ [26], перед их
передачей в опытную эксплуатацию, называемую по зарубежной терминологии бета-
тестированием. Эти действия повторяются также после устранения ошибок в
контролируемых программах, после обобщения и введения дополнительных
функций, а также после постановки в новую операционную среду и на новое
оборудование.
В процессе совершенствования программного продукта принято
нумеровать версии чаще всего цифровыми кодами состоящими из двух чисел.
Первое из них определяет номер версии внешних спецификаций, включающих
описание входных языков, набора реализованных функций типа процессора и т.п.,
второе нумерует способ реализации, определяемый встроенными структурами
данных и методами решения задач.
Язык Ассемблера в программировании информационных и управляющих систем 287
Процесс тестирования и коррекций программ, повторяемый итеративно
при усовершенствовании программ с целью устранения ошибок называют
отладкой программ. Отладка занимает большую часть времени разработки
программного обеспечения, что стимулирует крупные фирмы, такие как IBM на
создание автоматизированных технологий спецификаций, верификации и
тестирования. Однако эти технологии обычно используются как внутреннее know-
how ведущих фирм. Это не мешает свободным программистам и относительно
малым фирмам разрабатывать достаточно надежное программное обеспечение
для разных прикладных областей с использованием технологий массового
уровня.
Для организации тестирования строят контрольные примеры или тесты.
В качестве тестов могут использоваться данные, несущие как
информационную, так и управляющую нагрузку. Контрольные примеры строятся так, чтобы
обеспечить наиболее полную проверку работоспособности программ. Для
этого обеспечивается просмотр окрестность критических точек пространства,
создаваемых комбинацией данных вызывающих переключение трасс
выполнения программы. Критические точки пространства исходных данных
правильнее всего выбирать так, чтобы они охватывали наиболее полный диапазон
значений для каждой переменной и обеспечивали проверку как в крайних так и в
средних точках для разных экспериментов. Традиционная методика
планирования экспериментов предполагает изучение поведения модели объекта в
окрестностях контрольной точки экспериментов, составляющих гиперкуб (или
гипертетраэдр), окружающий центральную точку эксперимента с двумя
контролируемыми позициями по каждой оси и общим количеством экспериментов 2",
где п - количество управляемых параметров в эксперименте. При
планировании эксперимента по тестированию программного обеспечения желательно
определять эксперименты по трем точкам на каждом ребре гиперкуба входных
данных с общим количеством экспериментов 3".
Спектр промежуточных вариантов исходных данных в контрольных
примерах при известных направлениях переключений вычислительного
процесса дополняется дополнительными точками, связанными с окрестностями
проверяемых условий. Желательна проверка для каждого варианта условия. В
случае множественных ветвлений для гарантий корректности программ по всем
ветвям желательно иметь механизмы тестирования каждой из ветвей. Общую
методику подготовки тестов подробнее рассмотрим в следующем параграфе.
Определенный интерес представляет спектр гибридных методик,
ориентированных на использование разных доступных формализмов при
программировании и на этапе эксплуатации. К этому классу относятся средства
контроля допустимых значений данных ряда типов. Например, отладчики,
связанные с языками программирования высокого уровня, имеют возможность
отладочного контроля допустимости индексов элементов статически и динамически
определенных массивов. Это обеспечивается наличием соответствующих
расширенных спецификаций типов или за счет включения предварительных
операторов контроля допустимых значений входных данных. Такой подход удобен
при отладке и опытной эксплуатации программных продуктов. Дополнитель-
288 Глава 12 Отладка и тестирование программ на уровне машинных команд
ные описания и операторы контроля, как правило, могут быть сняты при
промышленной эксплуатации программ
Другой подход состоит в том, чтобы проверять результаты программ с
помощью специальных проверочных равенств или др\1их типов аналитических
выражений. Эти средства также могут быть встроены в отладчики или
вставлены в программу на этапе отладки или опытной эксплуатации с помощью
операторов условной трансляции. Такой фрагмент контроля на языке Ассемблера
может иметь вид:
IF выражение,, управляющее режимом отладки
Фрагмент чувствительного элемента распознавания особой
ситуации
Список отображаемых данных
CALL имя подпрограммы отображения в процессе отладки
ENDIF
Чувствительным элементом средств отладки также может быть
подпрограмма, проверяющая условия правильности входных данных или результатов
программы Накопление информации об особых ситуациях, включая ошибки в
программах может быть частично или полностью переложена и на средства
отладчиков
Общие методы решения мдачи отладки позволяют создать методику
предварительной подготовки как контрольных примеров, так и результатов их
выполнения или контрольных аналитических выражений для их моделей.
Способ построения контрольных выражений определяет уровень
контроля правильности:
• контрольные выражения, полученные обратными преобразованиями
вычислительных формул, позволяют проверить правильность
программирования в рамках выбранных аналитических моделей;
• контрольные выражения, полученные из аналитических моделей
путями, альтернативными вычислительным формулам, позволяют проверить
комплекс аналитических преобразований при программировании и
собственно программирование;
• контрольные выражения, составленные из неформальных соображений,
например по соблюдению законов сохранения на содержательном
уровне моделирования, ползволяют проверить комплекс преобразований,
включая результаты формализации моделей.
Такой подход к испытаниям программ может применяться только
внутри фирм разработчиков и фирм, занимающихся сертификацией, на основе
достаточно полной документации на программный комплекс. В реальной жизни
наиболее полный комплекс подобных действий выполняется при тестировании
опытной версии программного продукта (бета-версии) путем интенсивной
роботы с корректными и некорректными данными, подаваемыми на вход
программы. Отсутствие комплексной и целенаправленной методики проверок
приводит к тому, что даже продукция фирм лидеров в области разработки про-
Язык Ассемблера в программировании информационных и управляющих систем 289
граммного обеспечения доводится до достаточно надежных кондиций
довольно долго.
12.2. Составление контрольных примеров для
проверки правильности программ
Составление контрольных примеров, гарантирующих достаточную
достоверность проверяемых программ требует большого искусства и умения
экспериментатора. Эта задача существенно упрощается при использовании
технологии модульного программирования за счет сокращения размеров
тестируемых объектов. Однако организационно требуются дополнительные затраты на
тестирование объединенных программных комплексов.
Лишь в очень редких случаях программы удовлетворяют условиям
спецификаций во всем диапазонам возможных значений входных переменных в
соответствии с разрядной сеткой представления данных. Гораздо чаще
возникают ограничения, связанные с погрешностями методов и точностью
реализации программных ресурсов вычислительной системы. Поэтому при
проектировании контрольных примеров особую роль играет знание диапазонов
допустимых значений входных и выходных переменных. Составление абсолютно
полных тестов, проверяющих работу программы для всех возможных значений,
практически невозможно из-за колоссального количества возможных
комбинаций данных, поэтому достаточную полноту проверки программ обеспечивают
за счет систематизации тестируемых фрагментов программы и управления
режимами проверки.
Для проверки процедурных и функциональных модулей необходимо
проверять граничные значения по каждой переменной, границы критических
интервалов или критические точки данных, средние точки критических
интервалов. Множество контрольных примеров проверки независимого модуля
может быть построено как декартово произведение всех значений по всем
взаимно независимым координатам множества входных данных задачи. Главная
цель построения таких примеров - получить возможность локализации и
выявления причин ошибки в случае ее обнаружения.
Для того, чтобы составить контрольный пример необходимо
проанализировать структуру программы, выявив все точки и условия ветвлений и
циклов, для проверки корректности работы программ в окрестностях критических
значений проверяемых условий. Из значений промежуточных результатов
методом обратных преобразований получают критические комбинации значений
входных переменных, которые составляют подмноженство контролируемых
значений реализации. Второе подмножество входных значений,
контролирующих соблюдение условий спецификации, составляют триады точек по
интервалам допустимых значений каждой переменной. Контрольный пример включает
поочередное решение задачи для всех возможных контролируемых комбинаций
входных данных.
Факт ошибки выявляется по несовпадению заранее просчитанного
эталонного результата с фактическими результатами выполнения контрольного
примера. Даже небольшие отклонения в численных результатах иногда могут
290 Глава 12 Отладка и тестирование программ на уровне машинных команд
свидетельствовать о серьезных ошибках в программах. Поэтому в любом
случае необходимо выяснить причину отклонений результатов от ожидаемых
значений. Поиск причин отклонений по существу является начальной частью
процесса отладки. Для решения этой задачи нужны промежуточные результаты
вычислений для проверяемых значений исходных данных. Эти значения
обычно рассчитываются для некоторых контрольных точек задачи, в которых
проверяются промежуточные результаты.
Для локализации ошибки нужно внимательно просмотреть выполнение
программы и значения ключевых переменных в режимах трассировки и
проверки контрольных точек.
12.3. Программные опишдчики и
методика их эксплуатации
Для отладки программ применяются отладочные режимы
компиляторов, а также специальные универсальные программные и
аппаратно-программные отладчики. В прежние времена для отладки часто использовали режим
интерпретации языка специальными подпрограммами, имитировавшими
операторы языка. При этом могли возникать особые ситуации, связанные с
несоответствиями в компилирующей и интерпретирующей части системы.
Современный отладочный режим компилятора обычно обеспечивает выдачу
необходимых для отладки символических имен с их привязками к адресам загрузки в
объектный и загрузочный модули, что позволяет либо подгружать эти таблицы
при работе в отладочном режиме, либо пользоваться автономным отладчиком.
Оформление символической информации в виде таблиц позволяет их легко
отделять от загрузочного модуля при устранении режима отладки.
При разработке новых вычислительных и управляющих систем и
комплексов часто возникает необходимость проверки совместной работы
программного обеспечения и аппаратуры. При комплексной отладке важнее всего
выделить в аппаратной или программной части произошло ли отклонение от
режима нормальной работы, а также преодолеть возможности защищенного
режима по скрытию информации, необходимой для отладки. Роль аппаратуры
состоит в том, чтобы распознать и локализовать нарушения вычислительного
процесса.
В начальной стадии разработки отладочных программ различали
последовательные и экранные отладчики. Первые поставлялись с ассемблерами, а
вторые - в комплекте с трансляторами с языков высокого уровня. Каждый отладчик
имеет два основных режима работы: режим выполнения программы и режим
анализа результатов, а также систему отображения текущих результатов по именам
переменных, по их размещению в памяти и регистрах процессоров. В оконных
отладчиках для этих целей выделяются отдельные окна и предусматривается
возможность накопления трассы выполнения программы. В режиме анализа у
пользователя есть возможность переключаться между окнами за счет
унифицированного интерфейса отладчика.
Для реализации отладчиков в реальном режиме созданы специальные
прерывания режима трассировки и контрольной точки. В режиме трассировки
Язык Ассемблера в программировании информационных и управляющих систем 291
выполняемая программа переходит в режим прерывания по вектору I при
выполнении каждой очередной команды. Разработчик отладчика режима
трассировки после выполнения очередной команды должен внести изменения во все
отображения памяти и регистры, которые могли измениться в процессе
выполнения очередной команды или заново выполнить отображение всех регистров и
подключенных для отображения областей памяти. Команда перехода в режим
обработки контрольной точки или точки приостанова INT 3 имеет в
процессорах ix86 однобайтную модификацию. Простейший способ создания
контрольной точки в памяти - это обмен кода операции машинной команды в памяти на
команду INT 3 перед выполнением участка программы, такой подход
возможен практически всегда за исключением случаев записи программы в ПЗУ
(RAM).
Рациональная организация работы с отладчиками основывается на
чередовании основных режимов выполнения программы:
• выполнение программы до ближайшей контрольной точки при
первичном контроле или грубой локализации ошибки;
• выполнение программы в пошаговом режиме, если грубьщ анализ не
дал достаточной информации для локализации ошибки.
Контрольные точки имеют и еще одно применение при решении
больших задач, для которых велика вероятность сбоев аппаратуры за время их
решения. В таком случае задача может быть разбита контрольными точками на
участки, для которых можно повторять попытки решения, сохраняя
промежуточные результаты. Главное общее требование, предъявляемое к контрольным
точкам - это минимизация объема результатов, обеспечивающая сокращение
времени проверок и затраты на долговременное хранение промежуточных данных и
эталонов контрольных примеров.
Защищенный режим процессоров имеет специальные аппаратные
средства отладки, позволяющие отладчикам преодолевать аппаратно-программные
средства защиты. К этим средствам относятся, так называемые регистры
отладки, позволяющие сохранять разнообразные условия прерываний отладочых
вычислительных процессов.
Приведем общие принципы выполнения действий по локализации ошибки.
1. Локализацию ошибки в больших программных комплексах
целесообразно выполнять в режиме работы по контрольным точкам, расставляя их
начиная с самого высокого уровня функций и процедур. Поскольку необходимо
выяснить, в какой из функций возникли первичные отклонения, при большом
количестве функций на линейных участках необходимо придерживаться
принципа деления подозреваемых участков пополам по количеству процедурных
ошибок. Этот принцип пригоден для всех языков программирования.
2. Локализацию ошибки на малых участках программ, до десятка
команд, целесообразно выполнять путем анализа покомандной трассы или
пошагового выполнения программы.
Локализация ошибки при отладке на уровне языка Ассемблера
выполняется с точностью до одной машинной команды, в которой формируется не-
292 Глава 12 Отладка и тестирование программ на уровне машинных команд
правильный результат. Процесс локализации ошибок часто необходимо
сопровождать дополнительным расчетами или оценками промежуточных
результатов, которые могут не быть зафиксированы в контрольных примерах. Поэтому,
если контрольный пример просчитывался с применением компьютера или
калькулятора промежуточные результаты желательно сохранять для
возможного сравнения с результатами контрольных примеров.
12.4. Типичные ошибки и коррекция программ
Программист, практически работая в отладчике, визуально имеет дело
только с последствиями, а не с причинами ошибок. Наиболее
распространенные из них можно назвать в соответствии с нарастанием тяжести последствий
для функционирования ВК таких как:
• несущественные отклонения (порядка одного-двух младших значащих
битов) от эталонных результатов в районе границ областей допустимых
значений, для их учета могут быть уточнены точностные
характеристики спецификации программного модуля;
• редкие отклонения от правильной работы программы, которые могут
привести к тяжелым последствиям в работе системы управления или
информационной системы, чаще всего требующие дополнительного
изучения в режиме опытной эксплуатации или бета-тестирования;
• существенные отклонения с ошибками, охватывающими более 10%
разрядной сетки, или получение ошибочных результатов на определенных
наборах исходных данных, которые чаще всего требуют отладочных
действий, но в некоторых случаях могут быть условно допущены к
продолжению опытной эксплуатации;
• получение очевидно ошибочных результатов, не соответствующих
требованиям технического задания и спецификациям программ, эта и последующая
ситуации подлежат тщательному анализу до выяснения причин ошибок;
• аварийное завершение задачи с сообщением ОС, обработавшей это
завершение;
• зацикливание машины, снимаемое стандартным способом
принудительного прекращения задач в эксплуатируемой ОС;
• зависание машины, то есть невозможность продолжения нормального
функционирования без перезагрузки или изменения внутреннего
состояния системы.
Фактически в любом случае, за исключением первого целесообразно
продолжить отладку, чтобы выяснить причины ошибок. Отказ от таких
действий расширяет возможности проявления ошибок в условиях когда они могут
оказаться слишком существенными.
Симптомы проявления ошибок могут служить основой для определения
причины ошибок и подразделяются на регулярные, редкие и псевдослучайные.
Типовые причины появления ошибок при программировании это:
Язык Ассемблера в программировании информационных и управляющих систем 293
• неправильное понимание формальной модели предметной области и
задачи;
• ошибки и промахи при выполнении эквивалентных преобразований
аналитических связей предметной области;
• заблуждения в правилах выполнения операторов и машинных команд
на уровне Ассемблера;
• ошибки и промахи в использовании команд и распределении регистров
процессоров и памяти;
• ошибки контроля ограничений индексов, указателей, адресов и других
данных, используемых в программах на Ассемблере.
Ясно, что для точного выполнения условий задания на программу,
необходимо не только локализовать ошибку, но выявить и полностью осознать ее
причину. После этого можно продумать план коррекции ошибки. Только в
простейшем случае это будет замена одной или нескольких конкретных
команд. Коррекцию ошибки нужно делать не менее обстоятельно, чем
проектирование самой программы, потому что обнаруженная ошибка может указывать
не только на ошибки программиста и аналитика, ответственного за решение
задачи, но и на ошибки используемых системных программ, что к счастью
случается довольно редко, но требует значительно больших трудозатрат для их
доказательства и локализации.
Перед изучением методов коррекции систематизируется соответствие
типовых симптомов причинам ошибок и действий по их локализации в порядке
убыванию степени серьезности ошибочной ситуации.
1. Зависание однопрограммной системы, что чаще всего является
следствием зацикливания программы. Здесь причинами могут быть:
• неинициализированные области памяти и регистры с хранящейся в них
произвольной информацией - выявляется ошибочный цикл, параметры
которого проверяются через анализ областей памяти и регистров,
участвующих в проверке условий;
• разрушение содержимого областей памяти, в которых хранятся
прикладные и системные программы, ошибочными действиями - здесь
сначала делается попытка определить последний момент корректных
действий, а затем разрушенную область памяти и предшествующую причину
разрушения;
• логические ошибки и промахи программиста при программировании
циклов, включая неправильное расположение меток перехода и
запутанность логической структуры контролируемой программы;
• уход в незаглушенное необрабатываемое прерывание, некорректное
выполнение команд передачи управления или некорректное выполнение
неспецифицированных команд процессора - для их выявления
необходимо зафиксировать момент передачи управления путем определения
точки срыва вычислительного процесса.
Таким образом, процесс устранения ошибки типа зацикливания может
быть доказательно выполнен на основе анализа цикла, который не завершает-
294 Глава 12 Отладка и тестирование программ на уровне машинных команд
ся, а если этот цикл не имеет прямого отношения к решаемой задачи, то
причину входа в этот посторонний цикл. Для решения этой задачи необходимо
перейти в режим пошаговой отладки, выявив непосредственный момент входа в
цикл. Для этого можно использовать встроенные в отладчики счетчики
контрольных точек в соответствии с конкретными особенностями контрольного
примера или изменить контрольный пример к виду, позволяющему ускорить
локализацию ошибки.
Однако выявление момента вхождения в бесконечный цикл еще далеко
не всегда определяет причину его неправильной работы, особенно в том
случае, если причиной послужило разрушение памяти. При этом сначала
необходимо выявить разрушенную область-памяти, а затем выявить причину
разрушения, используя просмотр разрушаемой области на предшествующих этапах
выполнения программ по мере выполнения предыдущих циклов, а затем
просмотр линейных участков методом половинного деления.
2. Зацикливание, снимаемое оператором во многом похоже на
предыдущий случай с сохранением большей надежды на то, что не произошло
разрушений памяти. Та особенность современных систем, что они не сообщают
конкретных условий прерывания зацикленных программ, несколько затрудняет
локализацию обнаруженной ошибки и требует предварительного перехода в
отладочный режим. В следствие чего используются все методы локализации, что
и в предыдущем пункте.
3. Аварийное завершение как и зацикливание в современных системах не
приводит к выдаче достаточной информации для локализации и тем более для
определения причин ошибки. Аварийные завершения чаще всего возникают из-
за ошибок защиты памяти, связанных с неправильным определением
указателей на данные или их выходом за пределы выделенных областей памяти. При
разработке систем программ уровня операционных систем причиной таких
ошибок может послужить неправильное использование команд и структур
данных защищенного режима, в этом случае некоторые из расширителей DOS
дают информативный вывод содержимого регистров, что позволяет облегчить
выявление причины ошибки. Методика локализации ошибки та же, что и в
предыдущих случаях.
4. Локализация причин неправильных результатов работы программы
выполняются путем поэтапного анализа промежуточных результатов и поиска
первого отклонения результата от ожидаемых эталонов. Обнаружение обычно
выполняется методом половинного деления отлаживаемого фрагмента
контрольными точками до первой обнаруженного отклонения результата от эталона. В
редких случаях причинами таких отклонений могут быть частичные разрушения
программ и данных, о которых упоминалось выше. Признаками таких причин
может быть нерегулярность появления подобных ошибок и псевдослучайные
отклонения результатов.
5. Существенные отклонения результатов, проявляющиеся в отдельных
и редких ситуациях, требуют создания специальных контрольных примеров,
ориентированных на просмотр таких ситуаций.
Язык Ассемблера в программировании информационных и управляющих систем 295
В этих случаях от пассивных экспериментов по тестированию с заранее
заданными исходными данными контрольных примеров следует перейти к
тактике активных локальных экспериментов для проверки сомнительных
фрагментов программы. Локализация ошибки в этих случаях далеко не всегда сразу
определяет причину ошибки и многочисленные локальные эксперименты
позволяют выделить и определить ее причины более точно.
6. Малые отклонения в результатах численных программ чаще всего
обусловлены неправильной последовательностью выполнения действий с
плавающей точкой, которая ведет к существенной потере точности результатов.
Локализация ошибок здесь часто затруднена, так как они накапливаются при
выполнении множества операций. Другими причинами таких отклонений
могут быть неправильный выбор режима выполнения математических операций с
плавающей точкой, ошибки в реализации системных и библиотечных
программ, таких, как компиляторы и стандартные функции.
7. Редкие малые отклонения в результатах программ труднее всего
распознать и локализовать, но если они распознаны, их причины и
соответствующие способы их локализации чаще всего ге же, что и в предыдущем случае.
Один из важнейших вопросов состоит в быстрой и надежной
нейтрализации или устранении ошибок. Несмотря на то, что отладка программ и
исправление ошибок и замечаний по результатам опытной эксплуатации обычно
же ограничены жестким сроком, эти действия необходимо выполнять не менее
тщательно, чем первичное программирование. В порядке возрастания степени
радикальности необходимых действий рассмотрим меры по нейтрализации
ошибок в программах в зависимости от их причин.
1. Легче всего устраняются ошибки и промахи, возникшие при
подготовке программ, для этого вносятся соответствующие изменения в исходные
тексты программ.
2. Устранение ошибок в решении задач, вызванных некорректностями
работы системных программ и аппаратуры рекомендуется решать путем
обращения к фирме разработчику или дистрибьютеру некорректно работающей
компоненты. Однако в реальности программист не имеет возможности и
времени ожидать результатов таких коррекций. В этом случае он может найти
альтернативные корректные ресурсы для решения своей задачи и в соответствии с
ними выполнить коррекцию программы.
3. Если причиной ошибки является неправильное использование
аппаратного или программного ресурса по диапазону входных данных, то
необходимо провести более тщательный анализ диапазонов и выбранных алгоритмов
преобразования и только на основании такого анализа провести коррекцию. У
начинающих программистов часто возникает иллюзия возможности коррекции
таких ошибок за счет включения одного или нескольких дополнительных
условных операторов. Такой подход может вызвать цепь трудно нейтрализуемых
ошибок, приводящих к тому же к бесполезному росту размеров программы.
Из-за этого следует избегать подобных коррекций во всех случаях.
4. Если причиной ошибки явилось неправильное априорное
представление о работе аппаратного или программного ресурса, то для коррекции такой
296 Глава 12 Отладка и тестирование программ на уровне машинных команд
ошибки нужно заново проанализировать соответствующее место процесса
преобразования аналитических связей в алгоритм, а затем либо изменить метод
использования ресурса, либо заменить сам ресурс.
5. Если причиной ошибки явилось использование метода вычислений с
диапазоном допустимых ошибок, неадекватных решаемой задаче, то в ряде
случаев программист может изменить спецификации разработанных программ,
ограничив область их применения. Ч(аще всего, однако, приходится применять
последующие уточняющие методы решения или заново решать задачу более
подходящими.
Прокорректированная программа должна быть подвергнута полному
комплексу испытаний, какой бы простой не оказалась проведенная коррекция.
12.5. Основы апппаратно-программной отладки
Рассмотренный выше подход к отладке ориентирован на
программистов, работающих на надежных и стабильных вычислительных комплексах,
включая аппаратуру обмена. Однако для организаций, разрабатывающих
разнообразную нестандартную аппаратуру специального назначения важно уметь
распознать ошибки в работе таких устройств, а в дальнейшем локализовать и
нейтрализовать ошибки.
Подобные ошибки проявляются в неполной передаче данных, передаче
искаженных значений или выдаче избыточных неинформативных данных. Из
предыдущих материалов книги известно, что в цикле информационного
обращения задействована аппаратура внешних устройств, аппаратура системы
прерываний и, естественно, программы драйверов обмена информацией.
Традиционная методика отладки подобных комплексов предполагает разработку
специализированных тестовых программ для контроля аппаратно-программного
взаимодействия и последующих испытаний в рамках наиболее распространенных
системных программных пакетов.
Комплексная отладка аппаратуры и программ при обнаружении
ошибок требует проверки работоспособности в информационных стыках узлов
вычислительного комплекса. Поэтому локализация ошибки между аппаратурой и
программой в динамике реального времени вычислительного процесса может
выполняться только с помощью аппаратуры типа логических анализаторов,
позволяющих просмотреть трассы на шинах адреса для анализа правильности
процессов взаимодействия узлов аппаратуры. Трасса адресов позволяет
анализировать выполнение вычислительного процесса, связанное с
последовательностью выполненных команд. Искажение в последовательности адресов
свидетельствует об ошибке либо в программе, либо в функционировании
аппаратуры, чаще всего на стороне внешних устройств. Для доказательного
определения причин ошибки требуется дополнительная контрольно-измерительная
аппаратура.
В состав внешней аппаратуры для контроля информационных потоков и
трасс обычно включают кроме логических анализаторов осциллографы и
специальную аппаратуру сопряжения типа селективных контроллеров. При
проведении отладочных экспериментов такие контроллеры обычно управляются со
Язык Ассемблера в программировании информационных и управляющих систем 297
стороны инструментального (host) компьютера и обеспечивают накопления
трасс и синхронизацию отладочных прерываний для просмотра результатов
эксперимента.
Краткие итоги
Отладка требует серьезной организации эксперимента по локализации
причин ошибок, которые могут проявиться иногда только на уровне
выполнения машинной команды. Работа на уровне Ассемблера позволяет не только
проанализировать выполнение программ, но и анализировать работу
программ на аппаратном уровне.
ЛИТЕРАТУРА
*1. Лю Ю-Чжен, Гибсон Г. Микропроцессоры семейства 8086/8088.
Архитектура, программирование и проектирование микрокомьпютерных систем-
Пер с англ. - М.: Радио и связь, 1987. - 512 с , ил
*2. Морс С. П., Алберт Д. Д. Архитектура микропроцессора 80286: Пер.
с англ. - М.: Радио и связь, 1990. - 304 с.
*3. Смит Б. Э., Джонсон М. Т. Архитектура и программирование микро-
пропроцессора INTEL 80386* Пер. с англ. - М.: Конкорд, 1992. - 334 с, ил.
*4. Григорьев В. Л. Микропроцессор i486 Архитектура и
программирование (в 4-х книгах). Кн. 1. Программная архитектура - М.: ГРАНАЛ, 1993. - с.
346.
5. Григорьев В. Л. Микропроцессор i486. Архитектура и
программирование (в 4-х книгах). Кн. 2. Аппаратная архитектура. Кн. 3.Устройство с
плавающей точкой. Кн. 4 Справочник по системе команд - М.: ГРАНАЛ, 1993. -
с. 382.
6. Абель П. Язык Ассемблера для IBM PC и программирования: Пер. с
англ. - М.: Высш. шк., 1992 - 447 с
7. Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и
компиляции: Пер. с англ. - М.: Мир, Т. 1 Компиляция, 1978 - 616 с, Т. 2.
Компиляция, 1978.
8. Бек Л. Введение в системное программирование Пер. с англ - М
Мир, 1988.-448 с, ил.
9. Богумирский Б С. Руководство пользователя ПЭВМ В 2-х ч - СПб,
Ассоциация OILCO, 1992 - Ч. 1. - 357с , ил. Ч. 2. - 378 с , ил
10. Брамм П., Брамм Д. Микропропроцессор 80386 и его
программирование: Пер. с англ. - М Мир, 1990. - с, ил.
11. Браун Р., Кайл Дж. Справочник по прерываниям для IBM PC: В 2-х
томах: Пер. с англ. -М.: Мир, 1994.-Т 1.-558 с. Ч. 2 - 480 с
12. Брэдли Д Дж. Программирование на языке Ассемблера для
персональных ЭВМ - 1988.
13. Брябрин В. М. Программное обеспечение персональных ЭВМ - М.,
Наука, 1988, 1989.-272 с.
14. Вирт Н. Алгоритмы + структуры данных = программы Пер. сангл. -
М.:Мир, 1985.-406 с, ил
15. Вирт Н. Алгоритмы и структуры данных: Пер с англ - М.: Мир,
1989.-317 с, ил.
16. Гончарук С. К., Кудельский И. В , Пустоваров В. И. Выбор
оптимальной конфигурации задач в подсистеме сбора информации системы
контроля и диагностики Вестник КПП. Автоматика и электроприборостроение - К.1
"Либщь", 1992.- 28 с, с. 21-27
17. Григорьев В. Л. Программирование однокристальных
микропроцессоров. - М.: Энергоатомиздат, 1987. - 288 с.
18. Грис Д Конструирование компиляторов для цыфровых
вычислительных машин: Пер. с англ - М : Мир, 1975. - 544 с , ил
Литература 29Q
19. Дао Л. Программирование микропроцессора 8088: Пер. с англ. - М.:
Мир, 1988.-357 с, ил.
20. Джордейн Р. Справочник программиста персональных компьютеров
IBM PC, XT и AT: Пер. с англ. - М.: Финансы и статистика, 1992. - 544 с.
21. Зиглер К. Методы проектирования программных систем: Пер. сангл. -
М.:Мир, 1985.-328 с, ил.
22. Исида X. Программирование для микрокомпьютеров: Пер. с яп. -
М.:Мир, 1988.-224 с, ил.
23. Кейслер С. Проектирование операционных систем для малых ЭВМ:
Пер. с англ. - М.: Мир, 1986. - 680 с, ил.
24. Кнутт Д. Искусство программирования для ЭВМ: Т. 3. Пер. с англ. -
М.: Мир, 1978.
25. Кудельский И. В., Лиличенко С. А., Пустоваров В. И. Методика
эффективной организации поиска в информационных базах вычислительных
компплексов. Вестник КПИ. Автоматика и электроприборостроение - К.:
"Либщь", 1992.-29 с, с. 13-17
26. Липаев В. В. Проектирование программных средств: Учеб. пособие. -
М.: Высш. шк., 1990. - 303 с, ил.
27. Морс СП., Алберт Д. Д. Архитектура микропроцессора 80286: Пер.
с англ. - М.: Радио и связь, 1990. - 304 с.
28. Нортон Д. Написание драйверов для Windows: Пер. с англ. - М.:
Мир, 1994.-560 с.
29. Нортон П. Персональный компьютер фирмы IBM и операционная
система MS DOS: Пер. с англ. - М.: Радио и связь, 1991. - 416 с,ил.
30. Нортон П., Йао П. Программирование на Borland C++ в сре-
де\¥ик1о\У8: В 2-х томах: Пер. с англ. - К.: Диалектика, 1993. Т. 1. - 320 с, ил. Т.
2.-320 с, ил.
31. Орловский Г. В. Введение в архитектуру микропроцессора 80386 -
СПб: Сеанс-Пресс. Центр инфотехнологии Инфоком, 1992. - 240 с.
32. Петрухин В. С, Степанчиков Ю.А., Филин А. В. Персональные ЭВМ
на основе архитектуры intel 80386. В 2-х кн. - Обнинск: "ИНВЕСКО", 1993. Кн.
1.-336 с. Кн. 2.-256 с.
33. Погорелый С. Д., Слободянюк Т. Ф. Программное обеспечение
микропроцессорных систем. Справочник - К.: Техника, 1985, 1986. - 240 с, ил.
34. Пустоваров В. И. Принципы оптимизации программ для
многорегистровых микропроцессоров - Вестник КПИ. Автоматика и
электроприборостроение, 1979,16.
35. Пустоваров В. И. Оптимизация объектных программ с ипользовани-
ем макробиблиотек - Вестник КПИ. Автоматика и электроприбор о строение,
1980,17.
36. Пустоваров В. И. Управление критериями оптимизации в системах
автоматизации программирования - Вестник КПИ. Автоматика иэлектропри-
боростроение, 1984, 21.
37. Пустоваров В. И. Особенности внутреннего представления программ
в оптимизирующих компиляторах. Вестник КПИ. Автоматика иэлектроприбо-
ростроение - К.: "Вища школа", 1986, 23.
300 Язык Ассемблера в программировании информационных и управляющих систем
38. Пустоваров В. И. Реализация метаязыков в системах
программирования. Вестник КПИ. Автоматика и электроприбоостроение - К.: "Вища
школа", 1987,24.
39. Пустоваров В. И. Интеллектуальная концепция построениясистем
проектирования программ. Вестник КПИ. Автоматика и
электроприборостроение- К.:"Либщь", 1992, 29, с. 106-112.
40. Пустоваров В. И., Черновол В. И. Особенности алгоритмов
синтаксического анализа в компиляторах. Вестник КПИ. Автоматика иэлектроприбо-
ростроение- К.: "Вища школа", 1989, 26.
41. Пустоваров В. И., Прусс Г О., Гончарук С. К., Мусина Т. В.Эффек-
тивное представление типов данных к операций при реализации языков
программирования. Вестник КПИ. Автоматика и электроприбор о строение - К.:
"Либщь", 1990, 27, с. 17-22.
42. Пустоваров В. И., Тюрютиков А. И. Динамическое управление
приоритетами прикладных задач в системах реального времени. ВестникКПИ.
Автоматика и электроприборостроение -К.: "Вища школа", 1988, 25. с. 34-37.
43. Скэнлон Л. Персональные ЭВМ IBM PC XT. Программирование на
языке ассемблера: Пер с англ. - М.: Радио и связь, 1989. - 336 с, ил.
44. Фролов А. В., Фролов Г. В. Аппаратное обеспечение IBM/PC. В 2-х
ч. - М.: Диалог-МИФИ, 1993, Ч. 1. - 208 с. и Ч. 2. - 208 с.
45. Фролов А. В., Фролов Г. В. Защищенный режим процессоров Intel
80286/80386/80486. Практическое руководство по использованию защищенного
режима - М.: Диалог-МИФИ, 1993. - 240 с.
46. Чижов А. А. Систнмые программные средства ПЭВМ: Справочник -
М.: Финансы и статистика, СП Параграф, 1990. - 415 с.
47. Шагурин И. И., Бродин В. Б., Мозговой Г. П. 80386: описание и
система команд - М.: МП "Малип", 1992.- 160 с.
48. Шнайдер А. Язык Ассемблера для персонального компьютера
фирмы IBM - М.: Мир, 1988. - 406 с.
49. Шоу А. Логическое проектирование операционных систем: Пер.с
англ.-М.: Мир, 1981. - 360 с, ил.
50. Элфринг Г. Программирование на языке Ассемблера для микро-
ЭВМ - М.: Радио и связь, 1987. - 168 с.
51. Операционная система MS DOS (версия 3.3). Справочное
руководство - М., Кооператив "Программпродукт", 1989. - 305 с.
52. Микропроцессорний комплект К1810. Структура,
программирование, применение.: Справочная книга/ Под ред. Ю.М.Казаринова - М.:Высшая
школа, 1990.-304 с.
53. Использование Turbo Assembler при разработке программ. - К.:
Диалектика, 1994. - 288 с, ил.
54. Myers Т. J. Equations, models and programs. A mathematical
introduction to computer science - New Jersy: Prientice-Hall, 1988. - 512 p.
55. Scanlon L. J. IBM PC Assembly Language. A Guide forProgrammers -
Bowie: Prientice-Hall, 1985. - 310 p.
56. Bramm P., Bramm D. 80386 assembly language: a completetutorial and
subroutine library - TAB Books, 1988.
Литература 301
57. Murray W. H., Pappas С. Н. 80386/80286 assembly
languageprogramming - McGraw-Hill, 1986.
58. Murray W. H., Pappas С. Н. Assembly language programmingunder
OS/2 - McGraw-Hill, 1988.
59. Nelson R. P. The 80386 book - Microsoft Press, 1988.
60. Strauss E. Inside the 80386 - Brady, 1986.
61. Strauss E. 80386 technical reference - Brady, 1987.
62. Turley J.I. Advanced 80386 progamming techniques - McGraw-Hill, 1988.
63. i486 Processor progammer's reference manual - Intel, 1990.
64. Пустоваров В. I., Мусша Т. В. Системне програмування. Конспект
лекцш. Ч. 1, КШ, 1993. - 64 с.
ВЕК
Издание и продажа научно-технической литературы
в области программирования и схемотехники
Приглашает к сотрудничеству
и предлагает
Авторам:
• издание их произведений на выгодных условиях
Издательствам:
• участие в совместных изданиях
• обмен частями тиража
• прием товара на реализацию
Книготорговым фирмам:
• широкий ассортимент товара
• товарообмен
• гибкие условия оплаты
• размещение рекламы в наших изданиях
Тел./факс (044) 558-92-35
ДЛЯ ЗАМЕТОК
Учебное издание
В.И. Пустоваров
Язык ассемблера в программировании
информационных и управляющих
систем
Технические редакторы Сгирснко С Г , Ковгашок Ю С
Художник Ковтанюк Ю С
Корректор Тютюнник А А.
ИЧП "ЭНТРОГГ, лицензия №063389 от 24 мая 1994 года
Подписано в печать 22.01 98. Формат 60x84 1/16
Бумага офсетная. Гарнитура тайме
Печать офсетная. Тираж 8000 экз.
Заказ X? 436
Товарищество с ограниченной ответственностью "ВЕК"
252032, г. Киев, бульвар Т. Шевченко, 44, кв. 11
Тел./факс (044) 558-92-35
При участии фирмы "ДЕСС", Рогожский Вал, 15
Тел./факс 366-92-95
При участии МП "МАЛИП", г. Москва
Отпечатано с готовых диапозитивов в ГУП Издатсльско-
полшрафический комплекс «Ульяновский Дом печати»
432601, г. Ульяновск, ул. Гончарова, 14