KRIEGSSPIELE!
Вы хотите отреагировать на этот пост ? Создайте аккаунт всего в несколько кликов или войдите на форум.

Бек. Введение в системное программирование. 1988

Страница 1 из 2 1, 2  Следующий

Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:01 am

Л.БЕК
ВВЕДЕНИЕ В СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ
М.: "МИР", 1988

ГЛАВА 1. ОСНОВНЫЕ ПОНЯТИЯ

В данной главе содержится различная информация, которая послужит нам основой при изучении последующих глав, В разд.1.1 приводится обзор структуры книги и дается краткое введение в системное программное обеспечение. С разд.1.2 начинается обсуждение взаимосвязи между системным программным обеспечением и структурой ЭВМ, которое продолжится в дальнейшем на протяжении всей книги. В разд. 1.3-1.7 даются общие сведения об архитектуре некоторых ЭВМ, используемых далее в качестве примеров. Более детальное обсуждение большинства вопросов, касающихся архитектуры машин, можно найти в работах Танненбаум [1984], Пфлигер [1982] и Гир [1981].

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

1.1. ВВЕДЕНИЕ

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

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

Глубина изложения материала неодинакова. Главы, посвященные ассемблерам, загрузчикам и макропроцессорам, содержат достаточно детальную информацию, позволяющую подготовить читателя к тому, чтобы он мог самостоятельно написать данные компоненты программного обеспечения для реальных машин. С другой стороны, компиляторы и операционные системы являются слишком обширными темами, каждая из которых сама по себе не раз была объектом многих отдельных монографий и курсов. Очевидно, что невозможно полностью рассмотреть какую-либо из этих тем в одной главе сколько-нибудь разумного размера. Поэтому вместо этого предлагается введение в наиболее важные понятия и вопросы, относящиеся к компиляторам и операционным системам; особое внимание уделяется взаимосвязи программного обеспечения и структуры ЭВМ. Другие подтемы обсуждаются настолько, насколько позволяет место, и снабжаются ссылками на литературу, предназначенными для читателей, желающих изучить их более глубоко. Наша цель - дать хороший общий обзор рассматриваемых тем, который мог бы послужить в дальнейшем базой при изучении студентами специальных курсов по программированию. Аналогичный подход используется и при изложении материала гл.7.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:02 am

1.2. СИСТЕМНОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ И СТРУКТУРА ЭВМ

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

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

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

Каждая основная глава книги (гл.2-6) начинается с описания базовых функций обсуждаемого компонента системного обеспечения. Затем рассматриваются машинно-независимые функции, являющиеся расширением базовых. В заключительной части главы содержатся примеры реализации системных программ для реальных машин. Основные главы содержат следующие разделы:
1. Основные функции, свойственные данному типу программного обеспечения и не зависящие от реализации.
2. Возможности, наличие и особенности которых тесно связаны с машинной архитектурой.
3. Другие возможности, которые имеют относительную машинную независимость и характерны для большинства реализаций программного обеспечения данного типа.
4. Основные варианты построения конкретных компонентов программного обеспечения (например, однопросмотровый разбор в сравнении с многопросмотровым).
5. Примеры реализации для реальных ЭВМ, в которых основное внимание уделяется нестандартным свойствам программного обеспечения, связанным с машинными характеристиками.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:08 am

1.3. УПРОЩЕННАЯ УЧЕБНАЯ МАШИНА (УУМ)

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

1.3.1. СТРУКТУРА УУМ

ПАМЯТЬ. Оперативная память состоит из 8-разрядных байтов. Три последовательных байта составляют слово (24 разряда). УУМ имеет байтовую адресацию. Слова адресуются по адресу байта с наименьшим номером. Общий объем оперативной памяти составляет 32768 (2**15) байт.

РЕГИСТРЫ. Имеется пять регистров, у каждого из которых есть собственное назначение. В таблице, приведенной ниже, указаны мнемонические имена регистров, их номера и назначение. (Система нумерации регистров выбрана таким образом, чтобы была обеспечена совместимость с моделью УУМ/ДС).

Бек. Введение в системное программирование. 1988 8801610

Имя. Номер. Назначение
A. 0. Сумматор. Используется при выполнении арифметических операций
X. 1. Индексный регистр. Используется для адресации
L. 2. Регистр связи. Команда перехода на подпрограмму (JSUB) запоминает в этом регистре адрес возврата
PC. 8. Счетчик команд. Данный регистр содержит адрес очередной команды, выбираемой для исполнения
SW. 9. Слово состояния. Данный регистр содержит системную информацию, включая код условия (СС - Conditional Code)

ФОРМАТЫ ДАННЫХ

Значения целого типа хранятся в виде 24-разрядного двоичного числа. Для представления отрицательных чисел используется дополнительный код. Значения символьного типа хранятся в 8-разрядном коде ASCII (см. приложение Б). Аппаратные средства для выполнения действий над числами с плавающей точкой в стандартной модели УУМ отсутствуют.

ФОРМАТЫ КОМАНД

Все машинные команды стандартной модели УУМ имеют следующий 24-разрядный формат:

Бек. Введение в системное программирование. 1988 88016a10

8 разрядов - код операции(ор)
1 - признак x
15 - адрес (addr)

Признак х используется для задания индексного способа адресации.

СПОСОБЫ АДРЕСАЦИИ

Имеются два способа адресации. В команде они задаются разрядом x. Правила вычисления ЦЕЛЕВОГО АДРЕСА (target address) по адресу, заданному в команде, описываются нижеследующей таблицей. (Скобки используются для указания на содержимое регистра или ячейки оперативной памяти; например, (X) обозначает содержимое регистра X).

Бек. Введение в системное программирование. 1988 8801710

Способ адресации. Признак адресации. Вычисление целевого адреса
Прямая. x=0. ТА=addr
Индексная. x=1. TA=addr+(X)

СИСТЕМА КОМАНД

Для решения большинства простых задач вполне достаточно базового набора команд УУМ. Он включает команды загрузки регистра и записи его содержимого в память (LDA, LDX, STA, STX и т.п.), а также команды целочисленной арифметики (ADD, SUB, MUL, DIV). Все арифметические команды выполняются над содержимым сумматора и содержимым слова оперативной памяти. Результат остается в сумматоре. Специальная команда (СОМР - СОМРаге) служит для сравнения значения, содержащегося в сумматоре, со значением, хранимым в слове оперативной памяти. Эта команда устанавливает код условия СС, являющийся признаком результата сравнения (<, =, >). Команды условного перехода (JLT, JEQ, JGT) проверяют установленное значение СС и выполняют соответствующую передачу управления. Две команды предназначены для организации взаимодействия подпрограмм: JSUB - переход на подпрограмму с занесением адреса возврата в регистр L; RSUB - возврат по адресу, содержащемуся в регистре L.

В приложении A приведен полный список всех команд УУМ и УУМ/ДС с указанием их кодов и описанием выполняемых функций.

СРЕДСТВА ВВОДА-ВЫВОДА

В стандартной модели УУМ ввод и вывод выполняются побайтно. Для обмена используется самый правый байт сумматора. Каждому внешнему устройству присвоен уникальный 8-разрядный код. Существует три команды ввода-вывода. Каждая из этих команд в качестве своего операнда задает код устройства.

Команда проверки состояния устройства (TD - Test Device) проверяет, готово ли требуемое устройство передать или принять очередной байт данных. Для индикации результата проверки используется код условия. Значение кода условия "<" указывает на готовность устройства к обмену; значение "=" означает, что устройство занято; значение ">" означает, что данное устройство неисправно или не подключено к машине. Программа, желающая выполнить обмен, должна ждать до тех пор, пока устройство не будет готово, и только после этого она может выполнить команду чтения данных (RD - Read Data) или команду записи данных (WD - Write Data). Эта последовательность действий должна быть повторена для каждого байта данных, участвующего в обмене. Программа, показанная на рис.2.1 (гл.2), иллюстрирует такой способ выполнения обмена.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:11 am

1.3.2. СТРУКТУРА УУМ/ДС
ПАМЯТЬ

Структура памяти УУМ/ДС аналогична описанной ранее для УУМ. Однако максимальный объем памяти доступной в УУМ/ДС составляет 1Мбайт (2**20 байт). Такое увеличение требует изменения формата команд и способов адресации.

РЕГИСТРЫ
Дополнительные регистры УУМ/ДС приведены в следующей таблице:

Бек. Введение в системное программирование. 1988 8801810

Имя. Номер. Назначение
B. 3. Базовый регистр. Используется для адресации
S. 4. Общий рабочий регистр. Специального назначения не имеет
T. 5. То же
F. 6. Сумматор с плавающей точкой (48 разрядов)

ФОРМАТЫ ДАННЫХ

Наряду с форматами данных, которые есть в стандартной модели, УУМ/ДС предоставляет дополнительный формат для данных с плавающей точкой:

Бек. Введение в системное программирование. 1988 88018a10

1 разряд - s
11 - порядок
36 - мантисса

Мантисса интерпретируется как число между 0 и 1, т.е. предполагается, что двоичная точка стоит непосредственно перед ее старшим разрядом. Для нормализованных чисел старший разряд мантиссы должен равняться единице. Порядок интерпретируется как двоичное целое без знака в диапазоне от 0 до 2047. Если порядок имеет значение e, а мантисса - значение f, то абсолютная величина числа будет представлена как

f*2**(e-1024)

Знак числа с плавающей точкой указывается с помощью значения разряда s (0 - положительное число, 1 - отрицательное число). Машинный нуль представляется в виде слова, содержащего нули во всех разрядах.

ФОРМАТЫ КОМАНД

Большой объем оперативной памяти, доступный в УУМ/ДС, означает, что в общем случае 15-разрядного поля для задания адреса будет недостаточно. Таким образом, формат команд стандартной модели УУМ не подходит для УУМ/ДС. Существует два способа решения этой проблемы: либо использовать относительную адресацию, либо расширять адресное поле до 20 разрядов. Оба эти способа используются в УУМ/ДС (см. форматы 3 и 4 в нижеследующем описании). Кроме того, в УУМ/ДС имеются команды, которые вообще не ссылаются на оперативную память. Для этих команд используются форматы 1 и 2.

Ниже приведены форматы команд УУМ/ДС. Значения разрядов-признаков в форматах 3 и 4 будут рассмотрены при обсуждении способов адресации. Разряд e используется для того, чтобы различать форматы 3 и 4 (е=0 - формат 3, е=1 - формат 4). В приложении А для каждой команды УУМ/ДС указан номер ее формата.

Бек. Введение в системное программирование. 1988 8801910

Формат 1 (1 байт):
8 разрядов - код операции(ор)

Формат 2 (2 байт):
8 - код операции(ор)
по 4 - r1, r2

Формат 3 (3 байт):
6 - код операции (ор)
по 1 - n, i, x, b, p, e
12 - смещение (disp)

Формат 4 (4 байт):
6 - код операции (ор)
по 1 - n, i, x, b, p, e
20 - смещение (disp)

СПОСОБЫ АДРЕСАЦИИ

По сравнению со стандартной моделью в УУМ/ДС реализованы два новых способа относительной адресации, для которых используются командный формат 3. Их суть описывается следующей таблицей:

Бек. Введение в системное программирование. 1988 8802010

Способ адресации. Признаки адресации. Вычисление целевого адреса
Относительно базы. b=1, p=0. ТА = (B) + disp (0<=disp<=4095)
Относительно счетчика команд. b=0, p=1. ТА = (PC) + disp(-2048<=disp<=2047)

Для способа адресации ОТНОСИТЕЛЬНО БАЗЫ поле смещения в командном формате 3 интерпретируется как 12-разрядное целое без знака. Для способа адресации ОТНОСИТЕЛЬНО СЧЕТЧИКА КОМАНД это поле интерпретируется как 12-разрядное целое со знаком, причем отрицательные величины представляются в дополнительном коде.

Если в командном формате 3 разряды b и p одновременно установлены в 0, то в качестве целевого адреса используется значение поля disp. Для командного формата 4 разряды b и р должны оба равняться нулю, а целевой адрес берется из поля адреса команды. Мы будем называть такой способ прямой адресацией в отличие от относительной адресации, описанной выше.

Каждый из этих способов адресации может сочетаться с ИНДЕКСИРОВАНИЕМ адреса. Признаком индексирования является значение разряда x, равное 1, и в этом случае выражение для вычисления целевого адреса содержит дополнительное слагаемое (X). Заметим, что стандартная модель УУМ использует только прямую адресацию (с индексированием или без него).

Разряды i и n в форматах 3 и 4 определяют способ использования целевого адреса. Если i=1, а n=0, то собственно значение целевого адреса используется в качестве операнда без выполнения каких-либо дополнительных ссылок в оперативную память. Такой способ задания операнда называется НЕПОСРЕДСТВЕННОЙ адресацией. Если i=0, а n=1, то содержимое слова по целевому адресу используется в качестве адреса операнда. Такой способ называется КОСВЕННОЙ адресацией. Если разряды i и n оба равны 0 или 1, то целевой адрес задает местонахождение операнда. Мы будем называть такой способ ПРОСТОЙ адресацией. При использовании непосредственной и косвенной адресации использование индексирования невозможно.

Многие авторы используют термин ИСПОЛНИТЕЛЬНЫЙ АДРЕС (effective address) для обозначения того, что мы назвали целевым адресом команды. Однако имеется определенное противоречие, когда термин "исполнительный адрес" употребляется для команд, использующих косвенную адресацию. Для того чтобы избежать путаницы, мы в данной книге используем термин "целевой адрес".

Команды УУМ/ДС, в которых не используется непосредственная или косвенная адресации, переводятся ассемблером в машинные коды, имеющие в разрядах i и n значение 1. Ассемблер стандартной модели УУМ устанавливает в этих разрядах нулевые значения (так как 8-разрядный код операций всех команд стандартной модели заканчивается кодом 00). В УУМ/ДС предусмотрены специальные аппаратные средства для обеспечения упомянутой ранее совместимости "снизу вверх". Если оба разряда i и n имеют нулевое значение, то разряды b, р, е рассматриваются как часть поля адреса (а не как признаки способа адресации). Таким образом, командный формат 3 становится идентичным командному формату, который используется в стандартной модели УУМ, что и обеспечивает требуемую совместимость.

На рис. 1.1 приведены примеры различных способов адресации, которые обеспечиваются УУМ/ДС. На рис. 1.1a показано содержимое регистров В, PC и X, а также некоторых специально подобранных ячеек оперативной памяти. Значения приведены в шестнадцатеричном виде. Машинный код команды загрузки сумматора, ее целевой адрес и загружаемое значение показаны на рис. 1.1b. Вам следует тщательно изучить эти примеры и убедиться в том, что вы правильно понимаете различные способы адресации.

Бек. Введение в системное программирование. 1988 8802110 Бек. Введение в системное программирование. 1988 88021a10
Рис.1.1. Примеры команд и способов адресации УУМ/ДС

В приложении А дается полное описание всех форматов команд и способов адресации, используемых в УММ/ДС.

СИСТЕМА КОМАНД

УУМ/ДС обеспечивает выполнение всех команд стандартной модели. Дополнительно введены команды загрузки и запоминания содержимого новых регистров (LDB, STB и др.), а также команды арифметики с плавающей точкой (ADDF, SUBF, MULF, DIVF). Имеются команды, использующие в качестве операндов значения регистров. К ним наряду с командой пересылки содержимого одного регистра в другой регистр (RMO)] относятся арифметические команды, выполняющие действия над содержимым регистров (ADDR, SUBR, MULR, DIVR). Кроме того, введена специальная команда для обращения к супервизору (SVC). Выполнение этой команды вызывает прерывание, которое может быть использовано для связи с операционной системой. Подробно организация обращений к супервизору и прерывания будут обсуждаться в гл.6.

Имеются и другие дополнительные команды. Полный список всех команд УУМ/ДС с указанием их кода операции и выполняемых функций приведен в приложении А.

СРЕДСТВА ВВОДА-ВЫВОДА

Команды ввода-вывода, рассмотренные нами для стандартной модели, выполняются и на УУМ/ДС. Кроме того, данная модель имеет каналы ввода-вывода, которые могут работать независимо от центрального процессора. Это позволяет совместить операции обмена с процессом вычислений и тем самым повысить общую эффективность системы. Для начала (start), проверки (test) и прекращения (halt) канальных операций обмена используются соответственно команды SIO, TIO и HIO. Более детально эти вопросы будут обсуждаться в гл.6.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:23 am

1.4. СТРУКТУРА SYSTEM/370

В этом разделе дается краткое введение в System/370. System/370 представляет собой скорее архитектуру, нежели конкретную ЭВМ. Эта архитектура, реализованная на ряде различных машин, представляет модели семейства 370. Хотя эти модели различаются в аппаратной части и по другим физическим характеристикам, они логически совместимы друг с другом. Любая программа должна дать одинаковые результаты на любой ЭВМ семейства 370 при условии, что ее выполнение не связано с временными или другими машинно-зависимыми характеристиками. Архитектура System/370 совместима снизу вверх с машинами System/360, т.е. программы для System/360 должны правильно выполняться на ЭВМ System/370 (при вышеназванных условиях). Весьма похожие архитектуры были реализованы на некоторых других процессорах фирмы IBM и процессорах других фирм.

В этом разделе приводятся основные сведения, которые необходимы, чтобы понять примеры программного обеспечения System/370, обсуждаемые в дальнейшем. Дополнительную информацию можно найти в IBM [1983] и Страбл [1983].

ПАМЯТЬ

Память состоит из 8-разрядных байтов; каждый байт имеет уникальный адрес. Группа из последовательных 2 байт, первый из которых имеет адрес, кратный 2, называется ПОЛУСЛОВОМ. Аналогично группа из последовательных 4 байт, начинающаяся с адреса, кратного 4, называется СЛОВОМ, а группа из 8 байт, начинающаяся с адреса, кратного 8,- ДВОЙНЫМ СЛОВОМ. Таким образом, полуслово имеет длину 16 разрядов, слово - 32 и двойное слово - 64 разряда. Рис. 1.2 иллюстрирует такое разделение памяти на байты, полуслова, слова и двойные слова.

Бек. Введение в системное программирование. 1988 8802310
Рис. 1.2. Деление памяти System/370 на байты, полуслова, слова и двойные слова.

Машинные команды должны быть обязательно выравнены по границе полуслова (т.е. должны начинаться с адресов, кратных 2). В большинстве случаев операнды команд могут располагаться в памяти с любого адреса. Например, 16-разрядный операнд в команде АН (Add Halfword) может начинаться с любого места, т.е. он не должен выравниваться по границе полуслова. Однако скорость выполнения команд значительно выше, если операнды выравнены по границам, соответствующим их длинам. Максимальный объем оперативной памяти, который обычно доступен в System/370, составляет 16Мбайт (2**24 байт).

РЕГИСТРЫ

В System/370 имеется 16 регистров общего назначения, пронумерованных от 0 до 15. Для их обозначения часто пользуются символическими именами вида R0, ..., R15. Каждый регистр состоит из 32 разрядов. Для некоторых команд два последовательных регистра составляют один логический 64-разрядный операнд. Каждый регистр общего назначения может использоваться в качестве сумматора в арифметических и логических операциях. Кроме того, все эти регистры, за исключением R0, могут использоваться как базовые или индексные регистры. Имеется еще четыре других регистра, которые используются для операций с плавающей точкой. Каждый из этих регистров состоит из 64 разрядов. Некоторые команды используют два таких последовательных регистра для хранения 128-разрядных величин с плавающей точкой.

Все упомянутые выше регистры доступны в прикладных программах. В дополнение к ним есть 16 УПРАВЛЯЮЩИХ РЕГИСТРОВ, которыми пользуется операционная система. Существует также специальный регистр PSW (Program Status Word), содержащий различную системную информацию (счетчик команд, код условия и т.п.).

ФОРМАТЫ ДАННЫХ

System/370 обеспечивает хранение двоичных и десятичных целых, величин с плавающей точкой и символов. Символы хранятся в 8-разрядном коде EBCDIC. Двоичное целое хранится как 16-разрядная (полуслово) или 32-разрядная (слово) двоичная величина. В зависимости от команды эти величины могут рассматриваться как целое со знаком или целое без знака. Для представления отрицательных целых со знаком используется дополнительный код.

Десятичные целые представляются либо в ЗОННОМ ДЕСЯТИЧНОМ формате, либо в УПАКОВАННОМ ДЕСЯТИЧНОМ формате, В любом случае представление имеет переменную длину. Число используемых байтов выбирается программистом и зависит от величины максимального хранимого числа. В зонном десятичном формате четыре младших разряда каждого байта содержат двоичное представление десятичных цифр (от 0 до 9). Четыре старших разряда каждого байта, за исключением последнего, обычно содержат код 1111 (шестнадцатеричное F). В этом случае представление в виде зонного десятичного формата совпадает с представлением десятичных цифр в коде EBCDIC. Четыре старших разряда последнего байта могут интерпретироваться как знак. Обычно для положительных чисел эти разряды содержат шестнадцатеричный код C, для отрицательных - код D и для целых без знака (которые рассматриваются как положительные) - код F. Например, десятичное целое +53842 представляется в зонном десятичном формате как шестнадцатеричное F5F3F8F4C2 (5 байт), а -6071 - как F6F0F7D1 (4 байт). В упакованном десятичном формате каждый байт делится на два 4-разрядных поля. Во всех байтах, за исключением последнего, каждое из этих полей содержит двоичное представление десятичной цифры. В последнем байте первое поле содержит десятичную цифру, а второе - знак (по схеме, описанной выше). Таким образом, десятичное целое +53842 представляется в упакованном десятичном формате как шестнадцатеричное 53842С (3 байт), а -6071 - как 06071D (3 байт).

Величины с плавающей точкой представляются в одном из следующих форматов:

Бек. Введение в системное программирование. 1988 8802510

Короткий формат (слово):

1 разряд - s
7 - e
24 - f

Длинный формат (двойное слово):

1 разряд - s
7 - e
56 - f

Расширенный формат (два последовательных двойных слова):

1 разряд - s
7 - e1
56 - f1
1 - s
7 - e2
56 - f2

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

f = 16**(e-64)

Знак числа определяется значением разряда s (0 - положительное, 1 - отрицательное). Число нуль представляется кодом, содержащим нули во всех разрядах.

Отметим, что порядок чисел с плавающей точкой в System/370 интерпретируется как степень 16 (а не 2, как в большинстве машин). Это сделано для того, чтобы увеличить порядок чисел, которые могут быть представлены с помощью одного 32-разрядного слова. Поэтому мантиссу следует интерпретировать как шестнадцатеричное (а не двоичное) число. Это означает, что три старших разряда мантиссы могут равняться нулю (если старшая шестнадцатеричная цифра равна 1). В силу этого количество значащих разрядов в мантиссе может быть различным в зависимости от величины числа.

ФОРМАТЫ КОМАНД

В System/370 имеется восемь основных форматов команд. В этом разделе мы кратко остановимся на трех наиболее общих из них. Детальное описание всех восьми форматов можно найти в IBM [1983].

У большинства команд System/370 есть два операнда. Для этих команд существуют три возможных варианта расположения операндов в памяти: оба операнда в регистрах; один в регистре, другой в памяти; оба в памяти. Следующие форматы команд являются типичными для каждого из этих случаев.

Бек. Введение в системное программирование. 1988 8802610

Формат RR:
8 разрядов - op
4 - r1, r2

Формат RX:
8 разрядов - op
4 - r1, x2, b2
12 - d2

Формат SS:
8 разрядов - op
4 - l1, l2, b1
12 - d1
4 - b2
12 - d2

В формате RR оба операнда находятся либо в регистрах общего назначения, либо в регистрах с плавающей точкой. Номер регистра кодируется в команде (как r1 или r2). Тип регистра определяется кодом операции. Для команд формата RX один операнд находится в регистре rl, а другой - в оперативной памяти. Для задания полного адреса в System/370 требуется 24 разряда. Для того чтобы уменьшить объем памяти, занимаемый командой, исполнительный адрес (в общем случае) задается с помощью базового регистра b2, индексного регистра x2 и 12-разрядного смещения d2. (Порядок вычисления целевого адреса для каждого способа адресации будет описан ниже). В формате SS оба операнда расположены в памяти. Если команда данного типа ссылается на операнды переменной длины, то длина операндов задается в команде с помощью полей l1 и l2. Адреса операндов определяются с помощью базового регистра и смещения. Использование индексного регистра запрещено.

В некоторых других форматах команд поле кода операции расширено до 16 разрядов (форматы RRE и SSE). Формат RX можно изменить так, чтобы разрешить использование второго регистрового операнда вместо индексного регистра (формат RS) или чтобы включить 8-разрядный непосредственный операнд вместо двух номеров регистров (формат SI). Одна из модификаций формата SS определяет 8-разрядный код, который используется в обоих операндах. Другая модификация имеет только один операнд.

СПОСОБЫ АДРЕСАЦИИ

В System/370 команды, которые ссылаются на операнды, расположенные в оперативной памяти, должны использовать относительную адресацию с базированием. Целевой адрес получается как сумма содержимого базового регистра, индексного регистра (если он задан), и смещения. Смещение интерпретируется как 12-разрядное целое без знака (отрицательное смещение запрещено). Регистр общего назначения R0 нельзя использовать как базовый или индексный регистр. Если в команде задан в качестве базового или индексного регистра регистр с номером нуль, то при вычислении целевого адреса соответствующий регистр использоваться не будет. В System/370 применяются два способа непосредственной адресации. В команде LA (Load Address) целевой адрес загружается в заданный регистр (вместо того чтобы вызывать операнд из оперативной памяти). Некоторые другие команды, например такие, как MVI (MoVe Immediate), используют непосредственный однобайтовый операнд прямо из команды. Однако это особые случаи, когда непосредственная адресация определяется как часть команды. В большинстве команд использование непосредственных операндов невозможно.

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

СИСТЕМА КОМАНД

Команды System/370 делятся на пять классов: общего назначения, десятичной арифметики, арифметики с плавающей точкой, управляющие и ввода-вывода. Многие команды устанавливают 4-разрядный код условия для индикации различных ситуаций. Команды условного перехода могут проверить установленный код и выполнить соответствующее ветвление. Полный список команд System/370 с указанием устанавливаемых ими кодов условия можно найти в IBM [1983].

К командам общего назначения относятся команды загрузки и запоминания регистров общего назначения, а также команды, обеспечивающие выполнение двоичных арифметических операций и операций сравнения. Каждая из этих функций может выполняться с помощью нескольких различных команд в зависимости от типа и месторасположения используемых операндов. Например, двоичное сложение выполняется следующими машинными командами:
A - сложение слова, память-регистр;
AH - сложение полуслова, память-регистр;
AL - сложение слова, без знака, память-регистр;
ALR - сложение слова, без знака, регистр-регистр;
AR - сложение слова, регистр-регистр.

К группе команд общего назначения относятся также команды условного и безусловного переходов, логические операции, команды пересылки данных и многие другие операции. В System/370 имеется команда обращения к супервизору (SVC), похожая на аналогичную команду УУМ/ДС.

Команды десятичной арифметики могут быть использованы для выполнения арифметических операций над целыми в упакованном десятичном формате. Кроме того, имеются команды для преобразования зонного десятичного формата в упакованный и наоборот (команды PACK и UNPACK), а также команды преобразования двоичного целого в упакованный десятичный формат и обратно (команды CVD и CVB). Две другие команды предоставляют возможности для редактирования данных, выполняя, например, такие операции, как вставка запятой и десятичной точки в числовые величины.

Команды арифметики с плавающей точкой используются для выполнения операций над числами с плавающей точкой (загрузка и запоминание регистров с плавающей точкой, арифметические операции и операции сравнения). Так же как и в случае команд общего назначения, код операции зависит от типа и месторасположения операндов. Например, группа команд сложения включает следующие команды:
AD - сложение с нормализацией, длинный формат, память-регистр;
ADR - сложение с нормализацией, длинный формат, регистр-регистр;
АЕ - сложение с нормализацией, короткий формат, память-регистр;
AER - сложение с нормализацией, короткий формат, регистр-регистр;
AU - сложение без нормализации, короткий формат, память-регистр;
AUR - сложение без нормализации, короткий формат, регистр-регистр;
AW - сложение без нормализации, длинный формат, память-регистр;
AWR - сложение без нормализации, длинный формат, регистр-регистр;
AXR - сложение с нормализацией, расширенный формат, регистр-регистр.

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

СРЕДСТВА ВВОДА-ВЫВОДА

В System/370 обмен с внешними устройствами осуществляется с помощью каналов ввода-вывода, похожих на те, что были описаны для УУМ/ДС. Команды ввода-вывода позволяют центральному процессору (ЦП) начать, остановить и проверить каналы, а также выполнить другие управляющие операции. Имеется, кроме того, возможность, позволяющая ЦП выполнять прямую побайтную передачу с помощью специального интерфейса, не зависящего от каналов.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:24 am

1.5. СТРУКТУРА ЭВМ VAX

Семейство ЭВМ VAX было представлено фирмой DEC в 1978г. Аббревиатура VAX указывает на одну из наиболее важных особенностей данной архитектуры - виртуальное адресное расширение (Virtual Address extension). Хотя многие другие ЭВМ (включая System/370) были модифицированы для предоставления виртуальной памяти, система VAX с самого начала проектировалась в расчете на виртуальное адресное пространство. Виртуальная память позволяет программам работать так, как будто они имеют доступ к очень большой памяти, вне зависимости от объема реальной оперативной памяти, используемой в системе. Заботу об управлении памятью берет на себя операционная система. Мы обсудим виртуальную память в связи с изучением операционных систем в гл.6.

При проектировании архитектуры ЭВМ VAX была предусмотрена совместимость с более ранним семейством ЭВМ PDP-11. Средства совместимости обеспечены на уровне аппаратуры, что позволяет выполнять без каких-либо изменений многие программы PDP-11 на ЭВМ VAX. Более того имеется возможность одновременной работы в многопользовательском режиме программ для PDP-11 и VAX.

В этом разделе дается сводная информация о некоторых основных характеристиках архитектуры ЭВМ VAX. Дополнительную информацию можно найти в DEC [1981] и Баас [[1983].

ПАМЯТЬ

Память ЭВМ VAX состоит из 8-разрядных байтов, каждый из которых имеет свой адрес. Два последовательных байта составляют СЛОВО (word), четыре - ДЛИННОЕ СЛОВО (longword), восемь - КВАДРОСЛОВО (quadword), шестнадцать - ОКТОСЛОВО (octaword). Как и в System/370, для уменьшения времени доступа к оперативной памяти желательно, чтобы слова, длинные слова, квадрослова и октослова были выравнены по соответствующей границе.

Реальная оперативная память ЭВМ VAX может достигать 2**23 байт. В то же время все программы VAX работают в виртуальном адресном пространстве объемом 2*32 байт. Объем реальной оперативной памяти обычно никак не влияет на выполнение прикладных программ. Половина виртуального адресного пространства называется СИСТЕМНЫМ ПРОСТРАНСТВОМ (system space). Эта часть содержит программы операционной системы и используется совместно всеми программами. Другая половина адресного пространства называется ПРОСТРАНСТВОМ ПРОЦЕССОВ (process space). Пространство процессов определено отдельно для каждой программы. Часть этого пространства содержит стеки, доступные программе. Для работы со стеками имеются специальные регистры и команды.

РЕГИСТРЫ

В ЭВМ VAX есть 16 регистров общего назначения, которые обозначаются как R0-R15. В то же время некоторые из этих регистров имеют специальные имена и назначение. Длина каждого регистра общего назначения - 32 разряда. Регистр R15 используется как счетчик команд и имеет имя PC. Во время выполнения команды его значение изменяется таким образом, чтобы всегда указывать на очередной обрабатываемый байт команды. Регистр R14 используется в качестве УКАЗАТЕЛЯ СТЕКА (stack pointer) и именуется SP. Этот регистр указывает на вершину стека данной программы в пространстве процессов. Хотя для этой цели можно использовать и другие регистры, однако в машинных командах, косвенно обращающихся к стеку, всегда используется регистр SP. Регистр R13 используется как УКАЗАТЕЛЬ ФРЕЙМА (frame pointer) и носит имя FP. Соглашение о связях между процедурами в ЭВМ VAX построено на структуре данных, называемой стеком фреймов. При вызове процедуры адрес фрейма в стеке фреймов помещается в регистр FP. Регистр R12 имеет имя АР и используется в качестве УКАЗАТЕЛЯ АРГУМЕНТОВ (argument pointer). В соответствии с соглашением о связях при вызове процедуры этот регистр используется для передачи адреса начала списка фактических параметров.

У регистров R6-R11 нет специального назначения, и они могут использоваться в программах для обычных целей. Точно так же можно использовать и регистры R0-R5, но они, кроме того, применяются специальным образом в некоторых командах.

Кроме регистров общего назначения имеется регистр СОСТОЯНИЯ ПРОЦЕССОРА PSL (Processor Status Longword), который содержит переменные состояния и признаки, связанные с процессом. Наряду со многими другими информационными полями PSL содержит код условия и признак, указывающий на работу процесса в режиме совместимости с PDP-11. Есть также ряд управляющих регистров, употребляемых для поддержания различных функций операционной системы.

ФОРМАТЫ ДАННЫХ

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

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

В ЭВМ VAX предусмотрен упакованный десятичный формат, аналогичный соответствующему формату System/370. Имеется также ЧИСЛОВОЙ ФОРМАТ, в котором каждая цифра числа записывается в отдельном байте. В этом смысле числовой формат похож на зонный десятичный формат System/370, за исключением того, что цифры хранятся в коде ASCII, а не в EBCDIC. Поэтому обычно старшие четыре разряда каждого байта в этом формате имеют шестнадцатиричный код 3, а не F. В то же время числовой формат сложнее, чем зонный десятичный формат, так как знак числа может быть указан либо в последнем байте (как и в System/370), либо в отдельном байте перед первой цифрой. Эти две модификации называются соответственно СУФФИКСНЫМ ЧИСЛОВЫМ форматом (trailing numeric) и РАЗДЕЛЬНЫМ ПРЕФИКСНЫМ ЧИСЛОВЫМ форматом (leading separate numeric).

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

ФОРМАТЫ КОМАНД

В ЭВМ VAX используется формат команд переменной длины. Каждая команда состоит из кода операции (1 или 2 байт), за которым в зависимости от типа операции следует до 6 СПЕЦИФИКАЦИЙ ОПЕРАНДОВ. Каждая спецификация операнда использует один из способов адресации и задает некоторую дополнительную информацию для определения месторасположеняя операнда в памяти.

СПОСОБЫ АДРЕСАЦИИ

В ЭВМ VAX предусмотрены разнообразные способы адресации. За небольшим исключением, каждый из этих способов адресации может использоваться в любой команде. Операнд может задаваться либо непосредственно в регистре, либо по адресу, находящемуся в регистре. Если операнд задается по адресу, находящемуся в регистре, то после выполнения команды содержимое регистра может автоматически увеличиваться или уменьшаться на длину операнда. Предусмотрено несколько способов адресации относительно базы, в которых могут использоваться поля смещения различной длины. Если этот способ адресации используется с регистром PC, то мы получим способ адресации относительно счетчика команд. Каждый из этих способов адресации может включать в себя индексный регистр, а многие из них могут использоваться для определения косвенной адресации. Наконец, разрешены непосредственные операнды и различные способы адресации, предназначенные для специальных целей. Дальнейшую информацию можно найти в DEC [1981].

СИСТЕМА КОМАНД

Одной из целей разработчиков системы VAX было создание системы команд, симметричной в отношении различных типов данных. Значительная часть мнемонических имен команд формируется с помощью комбинации следующих элементов:
- префикса, определяющего тип операции;
- суффикса, определяющего тип операндов;
- модификатора (в ряде команд), задающего число операндов.

Например, команда ADDW2 является операцией сложения с двумя операндами, каждый из которых занимает длинное слово. Аналогично команда MULL3 является операцией умножения с тремя операндами, каждый из которых занимает одно слово. Команда CVTWL определяет операцию преобразования слова в длинное слово (в последнем случае подразумевается использование двух операндов). В большинстве команд операнд может располагаться в регистре, в памяти или непосредственно в самой команде. Один и тот же код операции используется независимо от месторасположения операндов. Этот подход является более гибким по сравнению с подходом в System/370, который требует различных кодов операций в зависимости от месторасположения операндов.

VAX обеспечивает все обычные типы команд для вычислений, пересылки данных, преобразования, сравнения, ветвления и т.п. Кроме того, существует ряд более сложных команд, чем те, которые имеются в большинстве ЭВМ. Во множестве случаев такие операции являются аппаратной реализацией часто используемых последовательностей команд для увеличения эффективности и скорости их выполнения. Например, VAX предоставляет команды групповой загрузки и запоминания регистров, а также команды для работы с очередями и строками битов переменной длины. Предусмотрены мощные команды для вызова и возврата из процедур. С помощью одной-единственной команды осуществляется запоминание содержимого заданной группы регистров, передача списка фактических параметров процедуре, управление указателями стека, фрейма и списка аргументов, а также установка маски для предотвращения ошибок в арифметических операциях. Более детальную информацию о системе команд VAX можно найти в DEC [1981].

СРЕДСТВА ВВОДА-ВЫВОДА

Обмен в ЭВМ VAX выполняется с помощью контроллеров ввода-вывода. Каждый контроллер имеет набор регистров состояния/управления и регистров данных, задающих пространство в физической оперативной памяти. Участок адресного пространства, на которое указывают регистры контроллера, называется ПРОСТРАНСТВОМ ВВОДА-ВЫВОДА.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пт Фев 05, 2021 11:28 am

1.6. СТРУКТУРА ЭВМ CYBER

В этом разделе мы рассмотрим архитектуру ЭВМ серий CYBER 70 и CYBER 170 фирмы CDC. Несмотря на различия моделей этих серий в аппаратной части, они совместимы на уровне программ. Структура ЭВМ CYBER в значительной степени схожа с архитектурой ЭВМ серии CDC 6000 с некоторыми дополнительными возможностями. Похожая архитектура реализована в ЭВМ серии CYBER 180, объявленной фирмой CDC в 1984г. Эти машины имеют другую длину слова и значительные улучшения в аппаратной части, однако предусматривают возможность для работы в режиме CYBER 70 и CYBER 170.

CYBER - это мультипроцессорная система. В ее состав входят ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР (ЦП) и несколько ПЕРИФЕРИЙНЫХ ПРОЦЕССОРОВ (ПП). Обычно ЦП занят обработкой пользовательских программ, в то время как ПП выполняют функции операционной системы. ПП может начать или прервать выполнение программы, осуществляемое ЦП, для выполнения управляющие функций. Каждый ПП имеет доступ к оперативной памяти ЦП и к устройствам ввода-вывода. Кроме того, каждый ПП имеет свою собственную память.

Структура памяти, регистры и система команд ПП и ЦЦ абсолютно разные и никак не связаны друг с другом. В то же время они могут обмениваться информацией через общую память. В этом разделе мы коснемся лишь характеристик ЦП, Подробную информацию о структурах ЦП и ПП можно найти в CDC [1981а] и Гришман [1974].

ПАМЯТЬ

Память центрального процессора CYBER состоит из 60-разрядных слов. Машина имеет словную адресацию. К полям внутри слова нет средств прямого доступа, за исключением небольшой группы команд, ориентированных на обработку символьной информации. Максимальный объем оперативной памяти 256К (2**18 ) слов.

РЕГИСТРЫ

Программа пользователя может работать с тремя группами регистров: A, B и X. В каждой группе имеется по восемь регистров, которые соответственно обозначаются A0-A7, B0-B7 и X0-X7. Длина регистров групп A и B - 18 разрядов. Обычно регистры группы А используются для адресации, а регистры группы В - в качестве индексных регистров или для хранения небольших целочисленных значений (например, счетчик цикла). Регистр B0 всегда содержит нулевой код. Длина регистров группы X - 60 разрядов. Они используются при выполнении большинства операций, а также для хранения величин, выбранных из оперативной памяти.

Между регистрами группы A и регистрами группы X установлено не совсем обычное соответствие. Если в регистр A1 заносится некоторое значение, то оно рассматривается как адрес слова в оперативной памяти, и в регистр X1 автоматически заносится содержимое слова оперативной памяти по этому адресу. Такое же логическое соответствие существует между регистрами A2-A5 и регистрами X2-X5. Если некоторый адрес заносится в регистр A6, то содержимое регистра X6 автоматически записывается в оперативную память по этому адресу. Аналогичное соответствие установлено между регистрами A7 и X7. Регистры А0 и X0 логически между собой не связаны.

ФОРМАТЫ ДАННЫХ

Целые величины хранятся в виде 60-разрядных двоичных чисел (хотя некоторые операции целочисленной арифметики используют только младшие 48 разрядов слова). Для предоставления отрицательных чисел используется обратный код. Символьная информация хранится в 6-разрядном представлении, которое называется дисплейным кодом CDC. Вследствие использования 6-разрядного символьного кода содержимое слова ЭВМ CYBER обычно представляется в виде восьмеричного {а не шестнадцатеричного) числа.

Числа с плавающей точкой представляются в следующем формате:

Бек. Введение в системное программирование. 1988 8803610

1 разряд - s
11 - e
48 - c

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

c*2**(e-1024)

Знак числа определяется разрядом s. Для положительных чисел он равен 0. Отрицательные числа представляются инвертированием всех разрядов слова. Величина нуль с плавающей точкой записывается в виде слова, имеющего нули во всех 60 разрядах.

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

ФОРМАТЫ КОМАНД

Для большинства команд ЦП ЭВМ CYBER используются следующие форматы:

Бек. Введение в системное программирование. 1988 88036a10

(15-разрядный формат):
6 разрядов - op
3 - i, j, k

(30-разрядный формат):
6 разрядов - op
3 - i, j
18 - addr

В этих форматах op является кодом операции. Регистры, используемые в качестве операндов, обозначены как i, j, k. Тип регистров определяется командой. Если один из операндов расположен в оперативной памяти, то необходимо использовать 30-разрядный формат. Поле addr в этом формате содержит полный 18-разрядный адрес.

Для некоторых команд поле кода операции логически расширено до 9 разрядов за счет поля i. В других командах поля j и k составляют одно поле, в котором могут задаваться маска или величина сдвига. Кроме того, есть специальный 60-разрядный формат для небольшой группы команд, ориентированных на обработку символьной информации. Используемые в этом формате поля различны для разных команд. Детальное описание этих форматов можно найти в CDC [1982а].

СПОСОБЫ АДРЕСАЦИИ

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

СИСТЕМА КОМАНД

Логически команды ЦП CYBER распадаются на несколько групп. Самая большая и наиболее часто используемая содержит команды установки регистров A, B и X. Иллюстрацией некоторых из возможных команд этой группы могут служить следующие команды:

SAi Aj+Bk
SAi адрес
SBi Bj+K
SXi Xj+Bk

Так, например, команда

SA3 A4+B1

помещает в регистр A3 сумму содержимого регистров А4 и B1. В свою очередь это вызовет загрузку регистра XЗ содержимым слова оперативной памяти по адресу, занесенному в A3, Команда

SA6 BETA

(где BETA - это метка, имеющая в качестве своего значения адрес в оперативной памяти) заносит значение BETA в регистр A6, что обеспечивает запоминание содержимого X6 по этому адресу. Команды установки могут заносить значение в регистр любой группы (A, B или X) и имеют различные возможности для задания своих операндов.

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

В ЭВМ CYBER предусмотрены команды безусловного перехода и два типа команд условного перехода. Команды первого типа проверяют значение, содержащееся в регистре X, и осуществляют передачу управления в зависимости от результата. Команды второго типа обеспечивают передачу управления в зависимости от результата сравнения содержимого двух регистров группы B. Понятия код условия в ЭВМ CYBER нет. Сравнение и передача управления осуществляются одной командой. Предусмотрена специальная команда передачи управления с возвратом для вызова подпрограмм. Адрес возврата хранится в памяти в первой команде вызываемой подпрограммы.

СРЕДСТВА ВВОДА-ВЫВОДА

Все операции обмена выполняются периферийными процессорами. Как это делается, а также некоторые другие общие вопросы взаимодействия между ЦП и ПП мы обсудим в гл.6.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Сб Фев 06, 2021 11:35 am

ГЛАВА 2. АССЕМБЛЕРЫ

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

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

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

В разд.2.2 мы изучим некоторые типичные расширения базовой структуры, которые диктуются аппаратными особенностями. Это будет сделано на примере ассемблера для УУМ/ДС, Конечно, ассемблер УУМ/ДС не покрывает все возможные машинно-зависимые свойства, однако он включает некоторые средства, которые наиболее часто встречаются в реальных машинах. Рассматриваемые здесь принципы и технические приемы могут быть легко применены к другим ЭВМ.

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

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

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

2.1. ОСНОВНЫЕ ФУНКЦИИ АССЕМБЛЕРА

На рис.2.1 приведена программа на языке ассемблера базовой модели УУМ. Различные варианты этой программы мы будем использовать на протяжении всей этой главы для иллюстрации различных свойств ассемблера. Номера строк используются только для ссылок и не являются частью программы, Эти номера помогают также установить соответствие между участками различных вариантов программы. Мнемонические имена команд, используемые в примере, были рассмотрены в разд. 1.3.1 и приведены в приложении А. Модификатор, ",Х", следующий за операндом (см. строку 160), указывает на использование индексной адресации. Строки, начинающиеся с *, являются комментариями.

Бек. Введение в системное программирование. 1988 8804110
Рис.2.1 Пример программы на языке ассемблера УУМ.

Кроме мнемонических имен машинных команд в примере использованы следующие директивы ассемблера:
START - Определяет имя и начальный адрес программы.
END - Указывает на конец исходной программы и (обычно) задает первую исполняемую команду программы.
BYTE - Формирует символьную или шестнадцатеричную константу, занимающую столько байтов, сколько необходимо для представления константы.
WORD - Формирует целую константу, занимающую одно слово.
RESB - Резервирует заданное количество байтов для данных.
RESW - Резервирует заданное количество слов для данных.

Программа состоит из трех подпрограмм. Главная программа вводит записи с устройства ввода (код устройства F1) и копирует их на устройство вывода (код 05). Она вызывает подпрограмму RDREC для ввода записи на буфер и подпрограмму WRREC для вывода записи из буфера на устройство вывода. Так как в УУМ имеются только команды RD и WD, то каждая подпрограмма должна передавать данные побайтно. Буфер необходим для согласования скорости обмена устройства ввода со скоростью выводного устройства (например, магнитного диска и устройства печати). В гл.6 мы увидим, как для выполнения этих же функций используются канальные программы и макрокоманды операционной системы УУМ/ДС. Признаком конца записи служит нулевой код. Признаком конца файла служит запись, имеющая нулевую длину. При обнаружении конца файла программа выдает признак конца файла EOF на устройство вывода и заканчивает свою работу возвратом управления вызвавшей программе (возможно, операционной системе).

2.1.1. ПРОСТОЙ АССЕМБЛЕР ДЛЯ УУМ

На рис.2.2 показана та же программа, что и на рис.2.1, но вместе с объектным кодом, сгенерированным для каждого предложения. В столбце "Адрес" даны шестнадцатеричные машинные адреса оттранслированной программы. Мы предполагаем, что начальный адрес программы равен 1000. Заметим, что в реальном листинге ассемблера комментарии будут, конечно, сохранены. В нашем примере они исключены только из-за недостатка места.

Бек. Введение в системное программирование. 1988 8804310
Рис.2.2. Объектный код для программы на рис.2.1.

Для перевода исходной программы в ее объектное представление необходимо выполнить следующие действия (не обязательно в указанном порядке):
1. Преобразовать мнемонические коды операций в их эквиваленты на машинном языке - например, перевести STL в 14 (строка 10).
2. Преобразовать символические операнды в эквивалентные им машинные адреса - например, перевести RETADR в 1033 (строка 10).
3. Построить машинные команды в соответствующем формате.
4. Преобразовать константы, заданные в исходной программе, во внутреннее машинное представление (например, в строке 80 оттранслировать EOF в 454F46).
5. Записать объектную программу и выдать листинг.

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

10 1000 FIRST STL RETADR

Мы не можем сразу оттранслировать это предложение, так как не знаем адрес, который будет присвоен метке RETADR.

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

Ассемблер наряду с трансляцией команд исходной программы должен также выполнять и так называемые директивы ассемблера (иногда их называют псевдокомандами). Эти директивы не переводятся непосредственно в машинные команды (хотя и могут оказывать влияние на объектную программу), а управляют работой самого ассемблера. Примерами таких директив могут служить предложения BYTE и WORD, которые служат для включения в объектную программу констант, и предложения RESB и RESW, обеспечивающие резервирование заданного пространства оперативной памяти. В нашей программе имеются и другие директивы - START, которая определяет начальный адрес объектной программы, и END, которая отмечает конец исходной программы.

В заключительной фазе своей работы ассемблер должен записать полученный объектный код на некоторое устройство вывода. Позднее эта объектная программа будет загружена в оперативную память для исполнения. Для представления объектной программы мы будем использовать простой формат, в котором определены три типа записей: запись-заголовок, тело программы и запись-конец. Запись-заголовок содержит имя программы, ее начальный адрес и длину. Тело программы содержит машинные команды и данные программы с указанием адресов их загрузки. Запись-конец отмечает конец объектной программы и определяет адрес, с которого следует начать исполнение программы (точку входа). (Данный адрес задается операндом предложения END. Если этот операнд не задан, то в качестве точки входа берется адрес первой исполняемой команды [Начальный адрес программы не обязательно совпадает с адресом первой выполняемой команды (точкой входа).- Прим. ред.]).

Ниже приведены форматы, которые мы будем использовать для записей объектной программы. Реализация форматов (номер столбца и т.п.) может быть любой, однако содержащаяся в них ИНФОРМАЦИЯ должна в той или иной форме присутствовать в объектной программе.

Запись-заголовок:
Столбец 1 - H
Столбцы 2-7 - Имя программы
Столбцы 8-13 - Начальный адрес программы (шестнадцатеричный)
Столбцы 14-19 - Длина программы в байтах (шестнадцатеричная)

Тело программы:
Столбец 1 - T
Столбцы 2-7 - Начальный адрес в данной записи (шестнадцатеричный)
Столбцы 8-9 - Длина данной записи в байтах (шестнадцатеричная)
Столбцы 10-69 - Объектный код (шестнадцатеричный)

Запись-конец:
Столбец 1 - E
Столбцы 2-7 - Адрес первой исполняемой команды объектной программы (шестнадцатеричный)

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

На рис. 2.3 показан объектный код, соответствующий рассмотренной нами программе (рис.2.2). На этом и последующих рисунках мы будем использовать символ ^ для указания границы полей. Конечно, в реальной объектной программе этого символа нет. Обратите внимание, что для адресов 1033-2038 нет соответствующего объектного кода. Эта память просто резервируется загрузчиком и затем используется программой во время ее исполнения. (Детальное обсуждение функций загрузчика будет приведено в гл.3).

Бек. Введение в системное программирование. 1988 8804510
Рис.2.3. Объектная программа, соответствующая рис.2.2.

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

Первый просмотр (определение имен):
1. Назначение адресов для всех предложений исходной программы.
2. Запоминание значений (адресов), присвоенных всем меткам, для последующего их использования на втором просмотре.
3. Выполнение некоторых директив ассемблера. (К ним относятся директивы, влияющие на адресацию, такие как BYTE, RESW и т.п.).

Второй просмотр (трансляция команд и генерация объектного кода):
1. Трансляция команд (перевод кодов операций и разрешение адресных ссылок).
2. Генерация данных, заданных директивами BYTE, WORD и др.
3. Выполнение тех директив ассемблера, которые не были обработаны на первом просмотре.
4. Запись объектного кода и выдача листинга.

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

2.1.2. ТАБЛИЦЫ И АЛГОРИТМЫ АССЕМБЛЕРА

Наш простой ассемблер использует две основные внутренние таблицы: таблицу кодов операций (OPTAB) и таблицу символических имен (SYMTAB). OPTAB используется для поиска мнемонических кодов операций и перевода их в эквивалентные им представления на машинном языке SYMTAB используется для хранения значений (адресов), присвоенных символическим именам.

Еще нам потребуется счетчик адреса (LOCCTR). LOCCTR это переменная, которая используется для назначения адресов. Начальное значение LOCCTR определяется операндом предложения START. После обработки очередного предложения исходной программы длина полученной команды или области данных прибавляется к LOCCTR. Таким образом, если мы встречаем помеченное предложение исходной программы, то значение этой метки определяется текущим значением LOCCTR.

Таблица кодов операций должна по крайней мере содержать мнемонические коды операций и их машинные эквиваленты, В более сложных ассемблерах эта таблица, кроме того, содержит информацию о длине и формате каждой команды. Во время первого просмотра OPTAB используется для поиска и проверки корректности задания кодов операций в исходной программе. Во время второго просмотра она используется для перевода мнемонических кодов в их машинное представление. Фактически в нашем простом ассемблере для УУМ оба эти процесса можно совместить в одном просмотре (неважно, в первом или во втором). Однако для машин, имеющих форматы команд переменной длины (например, УУМ/ДС), нам необходимо просматривать OPTAB и при первом просмотре для того, чтобы определить приращение переменной LOCCTR. При втором просмотре мы используем OPTAB для определения формата и других специальных характеристик ассемблируемой команды. В нашем случае мы решили придерживаться именно этой структуры, поскольку она типична для большинства ассемблеров.

Обычно OPTAB организуется в виде хеш-таблицы [В нашей литературе иногда используется термин "перемешанные таблицы",- Прим. ред.], в которой в качестве ключа используется мнемонический код oneрации. (Конечно, OPTAB строится заранее - при создании ассемблера, а не во время его работы). Такая организация очень удобна, поскольку обеспечивает быстрый поиск при минимальном числе сравнений. В большинстве случаев OPTAB представляет собой статическую таблицу, т.е. в процессе работы в ней не создаются новые элементы, а уже определенные не исключаются. В этом случае можно построить специальную хеш-функцию или другую структуру данных, обеспечивающую для конкретного набора ключей оптимальное время доступа. Однако чаще всего используются стандартные хеш-функции. Подробную информацию о построении хеш-таблиц можно найти в любом хорошем учебнике по структурам данных, например в Стендиш [1980] или Кнут [1973б].

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

Для повышения эффективности поиска и внесения новых имен SYMTAB обычно организуется также в виде хеш-таблицы. Поскольку операция исключения элемента из таблицы применяется крайне редко (скорее никогда), то вопрос об эффективности ее выполнения не возникает. Интенсивное использование SYMTAB на протяжении всей работы ассемблера требует тщательного подбора хеш-функции. Очень часто программисты используют много похожих друг на друга имен, например имена, начинающиеся или заканчивающиеся одними и теми же символами (такие как LOOP1, LOOP2, LOOPA), или имена, имеющие одинаковую длину (A, X, Y, Z). Важно, чтобы используемая хеш-функция обеспечивала хорошую работу с такими неслучайными именами. Часто хороший результат достигается путем деления входного ключа на простое число, равное длине таблицы.

Хотя в качестве входной информации на этапе второго просмотра можно использовать исходную программу, однако должна быть учтена и информация, полученная ранее - после первого просмотра (например, адрес предложения или признак ошибки). Поэтому обычно во время первого просмотра создается промежуточный файл, который содержит каждое предложение исходной программы вместе с его адресом, признаком ошибки и другой необходимой информацией. Промежуточный файл является входным для второго просмотра, Рабочая копия исходной программы, содержащаяся в промежуточном файле, может быть использована для того, чтобы сохранить результаты некоторых операций, выполненных во время первого просмотра (например, операции сканирования поля операндов для определения имен и способов адресации). Это дает возможность не выполнять их еще раз при втором просмотре. Аналогично для кодов операций и имен могут быть сохранены указатели на таблицы OPTAB и SYMTAB, что позволит избежать повторных операций поиска.

На рис. 2.4а и 2.4б представлены алгоритмы соответственно первого и второго просмотров нашего ассемблера. Эти алгоритмы, несмотря на то что они описывают простой ассемблер, являются основой для более сложных двухпросмотровых ассемблеров, которые мы рассмотрим позднее. Для простоты мы будем полагать, что исходная программа записана в фиксированном формате с полями МЕТКА, ОПЕРАЦИЯ и ОПЕРАНДЫ. Если какое-либо из этих полей содержит строку символов, представляющую число, мы будем обозначать это с помощью префикса # (например, #[OPERAND]).

Бек. Введение в системное программирование. 1988 8804710
Рис. 2.4а. Алгоритм первого просмотра ассемблера.

Бек. Введение в системное программирование. 1988 8804810
Рис.2.4б. Алгоритм второго просмотра ассемблера.

На данном этапе очень важно, чтобы вы досконально разобрались с алгоритмами, представленными на рис. 2.4a и 2.4б. Вам настоятельно рекомендуется тщательно разобраться в этих алгоритмах, применив их для ручной трансляции программы на рис.2.1 в ее объектное представление на рис.2.3.

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


Последний раз редактировалось: Gudleifr (Пн Фев 08, 2021 4:23 pm), всего редактировалось 1 раз(а)
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Сб Фев 06, 2021 11:47 am

2.2. МАШИННО-ЗАВИСИМЫЕ ХАРАКТЕРИСТИКИ АССЕМБЛЕРА

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

На рис.2.5 показано, как предыдущая программа (см. рис.2.1) может быть записана с использованием дополнительных возможностей системы команд УУМ/ДС. Косвенная адресация в нашем языке ассемблера задается добавлением к операнду префикса @ (строка 70). Непосредственный операнд обозначается префиксом # (строки 25, 55, 133). Для команд, в которых есть ссылка на оперативную память, обычно используется либо адресация относительно счетчика команд, либо адресация относительно базы. Директива ассемблера BASE (строка 13) используется в связи со способом адресации относительно базы. (Подробности и примеры см. в разд.2.2.1) В некоторых командах величина смещения в относительном способе адресации может быть очень большой и для ее размещения будет недостаточно поля 3-байтового командного формата. Для таких команд необходимо использовать 4-байтовый расширенный командный формат. На языке ассемблера этот формат задается добавлением к коду операции префикса + (см. строки 15, 35, 65). Ответственность за правильность спецификации команд расширенного формата возлагается на программиста.

Бек. Введение в системное программирование. 1988 8805210
Рис.2.5. Пример программы на языке ассемблера УУМ/ДС.

Основное отличие новой версии программы от программы для УУМ заключается в том, что в ней всюду, где это возможно, используются команды вида регистр-регистр (вместо команд вида регистр-память). Например, в строке 150 вместо предложения COMP ZERO использовано COMPR A, S, а в строке 165 вместо TIX MAXLEN использовано TIXR Т. Кроме того, всюду, где это возможно, используются непосредственные операнды и косвенная адресация (например, строки 25, 55, 70).

Эти изменения внесены для того, чтобы использовать преимущества архитектуры УУМ/ДС с целью увеличения скорости выполнения программы. Операции вида регистр-регистр быстрее, чем соответствующие операции вида регистр-память. Это связано с тем, что, во-первых, они занимают меньше места в памяти и, во-вторых, что более важно, не требуют дополнительных ссылок в память для выборки операндов. (Выборка операнда из регистра происходит намного быстрее, чем из оперативной памяти). Точно так же, когда используется непосредственный операнд, то при обработке команды он уже присутствует как ее составная часть, и, следовательно, нет необходимости в его выборке откуда-либо еще. Использование косвенной адресации часто позволяет экономить команду (см. строку 70). Вы можете заметить, что в то же время некоторые изменения требуют внесения в программу дополнительных команд. Например, замена команды COMP на COMPR в строке 150 вынудила нас добавить команду CLEAR в строку 132. Тем не менее в целом скорость выполнения программы возрастет, так как команда CLEAR выполняется только один раз для каждой читаемой записи, а команда COMPR (более быстрая, чем COMP) выполняется для каждого байта.

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

В разд.2.2.2 обсуждаются отличия версии ассемблера УУМ/ДС, имеющие косвенную связь с новыми аппаратными возможностями. Например, расширенный объем оперативной памяти УУМ/ДС позволяет предположить, что мы будем иметь достаточно места для одновременной загрузки и исполнения нескольких программ. Такое разделение машины между несколькими программами называется мультипрограммным режимом обработки. Этот режим позволяет более эффективно использовать аппаратуру ЭВМ. (Мы вернемся к этому вопросу в связи с операционными системами в гл.6.). Однако для того, чтобы полностью использовать преимущества мультипрограммного режима, мы должны иметь возможность загружать программу в любое свободное место оперативной памяти (а не в фиксированное место, определенное во время трансляции). При этом возникает необходимость перемещения программ в оперативной памяти. В разд.2.2.2 мы обсудим эту проблему в ее связи с ассемблером.

2.2.1. ФОРМАТЫ КОМАНД И СПОСОБЫ АДРЕСАЦИИ

В этом разделе мы рассмотрим трансляцию предложений исходной программы УУМ/ДС (рис.2.5) в соответствующее им объектное представление (рис.2.6), обращая особое внимание на обработку ассемблером различных командных форматов и способов адресации. Заметим, что предложение START задает теперь 0 в качестве начального адреса программы. Как будет сказано в следующем разделе, это является признаком перемещаемой программы. Однако процедура трансляции команд остается при этом такой же, как если бы программа действительно загружалась с адреса 0.

Бек. Введение в системное программирование. 1988 8805410
Рис.2.6. Объектный код для программы на рис.2.5.

Трансляция команд вида регистр-регистр, таких как CLEAR (строка 125) или COMPR (строка 150), не представляет никаких новых проблем. Ассемблер должен просто преобразовать мнемонические коды операций в их машинное представление (используя OPTAB) и заменить мнемонические имена регистров на их числовые эквиваленты. Так же как и для команд других видов, это делается во время второго просмотра. Преобразование имен регистров в их числовые значения может делаться с помощью отдельной таблицы. Однако часто бывает удобно использовать для этих целей таблицу имен. Для этого в SYMTAB заранее заносятся имена регистров (А, X и т.д.) и их значения (0, 1 и т.д.).

Для трансляции большинства команд вида память-регистр используется относительный способ адресации (либо относительно счетчика команд, либо относительно базы). В любом случае ассемблер должен вычислить смещение, которое является составной частью объектной команды. Оно вычисляется так, чтобы после его сложения с регистром счетчика команд (PC) или базовым регистром (B) получить требуемый целевой адрес. Конечно, величина полученного смещения должна быть не слишком большой, чтобы поместиться в 12-разрядном поле команды. Это означает, что величина смещения должна находиться в диапазоне от 0 до 4095 (для адресации относительно базы) или в диапазоне от -2048 до +2047 (для адресации относительно счетчика команд).

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

15 0006 CLOOP +JSUB RDREC 4B101036

адрес команды равен 1036. Это полный адрес, который хранится в команде вместе с признаком, указывающим на использование расширенного командного формата (разряд е установлен в 1).

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

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

10 0000 FIRST STL RETADR 17202D

Во время выполнения команд в УУМ/ДС (как и в большинстве ЭВМ) счетчик команд продвигается вперед после выборки очередной команды (но до ее выполнения). Таким образом, во время выполнения команды STL регистр PC будет содержать адрес следующей команды (в данном случае 0003). Из листинга мы также видим, что метка RETADR (строка 95) имеет адрес 0030. (Ассемблер, естественно может получить эту информацию из SYMTAB). Необходимое нам смещение вычисляется как 30-3 = 2D. Во время выполнения этой команды ее целевой адрес будет вычисляться как (ЗС)+disp, что и даст нужный нам результат (0030). Обратите внимание, что разряд p установлен в 1, что является признаком адресации относительно счетчика команд. Таким образом, два последних байта машинной команды содержат код 202. Тот факт, что в данной команде не используется ни косвенная адресация, ни непосредственное задание операнда, указывается установкой 1 в разряды n и i. Поэтому первый байт машинной команды содержит код 17, а не 14 (см. описание значений разрядов-признаков на рис.1.1 в разд.1.3.2).

Другим примером адресации относительно счетчика команд может служить предложение

40 0017 J CLOOP 3F2FEC

Здесь адрес операнда равен 0006. Во время выполнения команды регистр PC будет иметь значение 0001A. Величина смещения вычисляется как 6-1А = -14. Так как это отрицательное число, то оно будет представлено в дополнительном коде. В машинной команде для поля смещения отводится 12 разрядов. Поэтому смещение будет записано как FEC.

Аналогичным образом вычисляется смещение для адресации относительно базового регистра. Основное отличие заключается в том, что ассемблеру известно значение счетчика команд на момент выполнения программы, в то время как значение базового регистра определяется программистом. Поэтому программист должен сообщить ассемблеру значение базового регистра на момент выполнения программы. В нашем примере это делается с помощью директивы ассемблера BASE. Данное предложение (строка 13) сообщает ассемблеру, что базовый регистр будет содержать адрес метки LENGTH. Предшествующая команда LDB #LENGTH загружает это значение в регистр во время работы программы. До тех пор, пока ассемблер не встретит другую команду BASE, он будет полагать, что значение базового регистра не меняется. Возможно, что в последующих фрагментах программы будет желательно использовать регистр B для других целей (например, для временного хранения некоторого значения). В этом случае программист должен использовать другую директиву ассемблера (например, NOBASE) для того, чтобы сообщить ему, что содержимое базового регистра теперь нельзя использовать для базирования.

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

160 104E STCH BUFFER,X 57C003

В соответствии с предложением BASE регистр B будет содержать во время исполнения программы значение 0030 (адрес LENGTH). Адрес метки BUFFER равен 0036. Таким образом, смещение определяется как 36-33 = 3. Заметим, что в машинном представлении команды оба разряда X и B установлены в 1, что является признаком адресации относительно базы с индексированием. Другим примером может служить команда STX LENGTH (строка 175). Здесь смещение равно 0.

Отметим различия в трансляции предложений в строках 20 и 175. В строке 20 для предложения LDA LENGTH использована адресация относительно счетчика команд. В строке 175 для STX LENGTH использована адресация относительно базы. (Если вы вычислите для этой команды смещение относительно счетчика команд, то убедитесь, что его значение слишком велико для 12-разрядного поля). Для предложения в строке 20 можно использовать оба способа адресации. Однако наш ассемблер вначале делает попытку использовать адресацию относительно счетчика команд. Выбор приоритета того или иного способа адресации достаточно произволен и устанавливается разработчиком ассемблера.

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

55 0020 LDA #3 010003

Операнд 003 хранится непосредственно в команде. Разряд i установлен в 1, что является признаком непосредственного операнда. Другим примером может служить команда

133 103C +LDT #4096 75101000

В данном случае операнд (4096) слишком велик для 12-разрядного поля и поэтому используется расширенный командный формат. (Если бы операнд был велик и для 20-разрядного поля, то мы не могли бы задать его непосредственно в команде).

Другой способ использования непосредственного операнда показан в команде

12 0003 LDB #LENGTH 69202D

В этом предложении непосредственный операнд задан ИМЕНЕМ LENGTH. Поскольку его ЗНАЧЕНИЕМ является АДРЕС, то в результате выполнения этой команды в регистр B будет загружен адрес, назначенный переменной LENGTH. Заметим, что в данном случае используется комбинация адресации относительно счетчика команд с непосредственным заданием операнда. Хотя на первый взгляд такая адресация может выглядеть не совсем обычной, тем не менее она не противоречит предыдущим рассуждениям. Дело в том, что в общем случае всегда вначале вычисляется ЦЕЛЕВОЙ АДРЕС, и если в команде установлен признак непосредственного операнда, то этот адрес (а не СОДЕРЖИМОЕ по этому адресу) будет использован в качестве операнда. (В предложении LDA в строке 55 разряды x, b и р установлены в 0. Таким образом, целевой адрес просто совпадает со смещением 003).

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

2.2.2. ПЕРЕМЕЩЕНИЕ ПРОГРАММ

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

Программа, которую мы рассмотрели в разд.2.1, представляет собой пример абсолютной программы (или абсолютного ассемблирования). Для того чтобы она правильно выполнялась, ее необходимо загрузить с адреса 1000 (т.е. с адреса, назначенного при ассемблировании). Чтобы убедиться в этом, рассмотрим команду

55 101В LDA THREE 00102D

из программы, представленной на рис.2.2. В объектной программе (рис.2.3) этому предложению соответствует команда 00102D, которая обеспечивает загрузку содержимого оперативной памяти по адресу 102D в регистр A. Предположим, что мы попытались загрузить и выполнить эту программу с адреса 2000 (вместо 1000). В этом случае адрес 102D не будет содержать ожидаемого значения. Скорее всего данный адрес будет принадлежать какой-либо другой пользовательской программе.

Очевидно, для того чтобы программа могла правильно работать, начиная с адреса 2000, мы должны сделать определенные изменения в адресной части данной команды. С другой стороны, некоторые фрагменты программы (например, константа 3, сгенерированная в строке 85) должны остаться неизменными вне зависимости от начального адреса загрузки. Если рассматривать объектный код отдельно, то в общем случае невозможно определить, какие значения представляют адреса, зависящие от месторасположения программы, а какие - неизменяемые объекты.

Поскольку ассемблеру не известен фактический адрес начала загрузки, он не может выполнить необходимую настройку адресов, используемых программой. Однако ассемблер может указать загрузчику те части объектной программы, которые нуждаются в настройке при загрузке. Объектная программа, содержащая информацию, необходимую для выполнения подобной модификации, называется ПЕРЕМЕЩАЕМОЙ программой.

Для того чтобы детально обсудить возникающие здесь проблемы, рассмотрим программу на рис.2.5 и 2.6. В предыдущем разделе мы ассемблировали данную программу с нулевым начальным адресом. На рис.2.7а показана ее загрузка с адреса 0. Команда JSUB (строка 15) загружается, начиная с адреса 0006. Ее адресное поле содержит значение 01036, которое является адресом команды с меткой RDREC. (Это, конечно, те же самые адреса, которые были назначены ассемблером).

Бек. Введение в системное программирование. 1988 8806010
Рис.2.7. Примеры перемещения программ.

Предположим теперь, что мы хотим загрузить эту же программу с адреса 5000, как показано на рис.2.7б. Теперь команда с меткой RDREC имеет адрес 6036. Таким образом, адресная часть команды JSUB должна быть модифицирована так, как показано на рисунке. Аналогично, если бы мы загрузили программу с адреса 7420 (рис.2.7в), то потребовалось бы приведение команды JSUB к виду 4B108456, чтобы ее адресная часть соответствовала новому значению адреса RDREC.

Обратите внимание, что независимо от того, куда загружается программа, разница между адресом RDREC и началом программы составляет 1036 байт. Это означает, что мы можем решить проблему перемещения программы следующим образом:
1. Когда ассемблер генерирует объектный код команды JSUB, мы полагаем, что он заносит адрес метки RDREC относительно начала программы. (Вот почему мы установили вначале счетчик адреса равным 0).
2. Ассемблер, кроме того, вырабатывает команду для загрузчика, которая предписывает ему прибавить к содержимому адресного поля команды JSUB начальный адрес программы.

Естественно, что команда загрузчика должна быть составной частью объектной программы. Это можно сделать с помощью дополнительного типа записи объектного формата - записи-модификатора. Эта запись имеет следующий формат;

Запись-модификатор:
Столбец 1 - M.
Столбцы 2-7 - Начальный адрес модифицируемого адресного поля относительно начала программы (шестнадцатеричный).
Столбцы 8-9 Длина модифицируемого адресного поля в полубайтах (шестнадцатеричная).

Длина хранится в полубайтах (а не в байтах), так как модифицируемое адресное поле не обязательно содержит целое число байтов. (Например, адресное поле в рассмотренной выше команде JSUB занимает 20 разрядов, что составляет 5 полубайтов). Месторасположение модифицируемого адресного поля задается адресом байта, в котором содержатся старшие разряды адресного поля. Если модифицируемое адресное поле занимает нечетное количество полубайтов, то предполагается, что оно начинается с середины байта, адрес которого задан в столбцах 2-7.

Эти соглашения, естественно, тесно связаны с архитектурой УУМ/ДС. Для других типов машин представление длины адресного поля в полубайтах может оказаться неудобным (см. упр.2.2.7).

Для команды JSUB, которую мы используем в качестве примера, запись-модификатор должна иметь вид

M00000705

Она указывает на то, что начальный адрес программы необходимо добавить к полю, которое начинается с адреса 00007 (относительно начала программы), и что длина адресного поля равна 5 полубайтам. Таким образом, в ассемблированной команде 4B101036 первые 12 разрядов (код 4В1) останутся неизменными. Адрес начала загрузки программы будет добавлен к последним 20 разрядам (01036) для того, чтобы получить правильный адрес операнда. (Вам следует самостоятельно убедиться в том, что это даст результат, показанный на рис.2.7).

Точно такие же действия необходимо выполнить для перемещения команд, заданных в строках 35 и 65 (рис.2.6), Остальные команды не требуют модификации при загрузке. В одних случаях потому, что операнды команд расположены не в оперативной памяти (например, CLEAR S или LDA #3). В других потому, что для задания операнда использована относительная адресация. Например, команда в строке 10 (STL RETADR) ассемблируется с помощью адресации относительно счетчика команд со смещением 02D. Неважно, с какого адреса будет фактически загружена программа. В любом случае слово, помеченное меткой RETADR, будет смещено на заданную величину (2D) относительно команды STL. Поэтому данную команду модифицировать не нужно, так как во время ее выполнения счетчик команд будет содержать реальный адрес следующей команды и, таким образом, процесс вычисления целевого адреса обеспечит получение правильного реального адреса, соответствующего RETADR.

Точно так же расстояние между LENGTH и BUFFER всегда будет равно трем байтам. Таким образом, после смещения команды в строке 160, использующей адресацию относительно базы, не будет требовать модификации при перемещении программы. (Содержимое базового регистра будет, конечно, зависеть от месторасположения программы однако его значение устанавливается автоматически при выполнении команды LDB #LENGTH, в которой используется адресация относительно счетчика команд).

Теперь нам абсолютно ясно, что модификация во время загрузки требуется только в командах, использующих прямую (как альтернатива относительной) адресацию. Для программ УУМ/ДС прямая адресация используется только в расширенном (4-байтовом) командном формате. Это существенное преимущество относительной адресации. Если бы мы попытались переместить программу, приведенную на рис.2.1, то увидели бы, что почти все команды требуют модификации.

На рис. 2.8 показано объектное представление, соответствующее исходной программе на рис.2.5. Обратите внимание, что записи тела программы остались такими же, как если бы они были подготовлены абсолютным ассемблером (с нулевым начальным адресом). Однако теперь адреса загрузки рассматриваются не как абсолютные, а как относительные величины. (То же самое, конечно, справедливо для адресов в записях-модификаторах и записи-конце). Для каждого адресного поля, требующего настройки при перемещении программы, имеется своя запись-модификатор (в нашем случае их 3 и они все для команд +JSUB). Вам следует самостоятельно проверить каждую запись-модификатор и убедиться, что вы поняли, как она формируется. В гл.3 мы детально рассмотрим, как загрузчик осуществляет требуемую модификацию программ. Сейчас важно, чтобы вы хорошо уяснили изложенные здесь концепции, так как они послужат основой для материала, обсуждаемого в следующем разделе.

Бек. Введение в системное программирование. 1988 8806210
Рис.2.8. Объектная программа, соответствующая рис.2.6.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Сб Фев 06, 2021 11:54 am

2.3. МАШИННО-НЕЗАВИСИМЫЕ ХАРАКТЕРИСТИКИ АССЕМБЛЕРА

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

В разд.2.3.1 мы обсудим таблицы и алгоритмы, необходимые для реализации литералов. В разд.2.3.2 будут рассмотрены две директивы ассемблера (EQU и ORG), основной функцией которых является определение имен. В разд.2.3.3 коротко рассматриваются выражения в языке ассемблера. Обсуждаются различные типы таких выражений, их вычисление и использование.

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

2.3.1. ЛИТЕРАЛЫ

Очень часто бывает удобно иметь возможность записывать значения константы, используемой в качестве операнда, непосредственно в команде, где она используется. Это позволяет отказаться от использования отдельного предложения для определения константы и соответствующей ей метки. Такой операнд называется литеральным (literal), поскольку значение константы задается в виде строки символов. На рис.2.9 приведен пример использования литералов. Объектный код, сгенерированный для предложений этой программы, показан на рис.2.10. (Эта программа является модификацией программы, представленной на рис.2.5; другие изменения мы обсудим в разд.2.3).

Бек. Введение в системное программирование. 1988 8806410
Рис.2.9. Программа, демонстрирующая дополнительные возможности ассемблера.

Бек. Введение в системное программирование. 1988 8806510
Рис.2.10. Объектный код для программы на рис.2.9.

В нашем языке ассемблера литералы задаются с помощью префикса =, за которым следует константа, задаваемая точно так же, как в предложении BYTE. Таким образом, в предложении

45 001A ENDFIL LDA =C'EOF' 0320I0

литерал определяет 3-байтовый операнд, значением которого является строка символов EOF. Аналогично в предложении

215 1062 WLOOP TD =X'05' E32011

задается 1-байтовый литерал, имеющий шестнадцатеричное значение 05. Символьная нотация для задания литералов может быть различной, однако в большинстве ассемблеров для того, чтобы упростить идентификацию литералов, используется определенный символ (у нас =).

Важно уяснить различие между литералом и непосредственным операндом. В случае непосредственного операнда его значение транслируется как составная часть машинной команды. В случае литерала его значение генерируется в виде константы в оперативной памяти, а ее АДРЕС используется в качестве целевого адреса команды. Употребление литерала дает точно такой же результат, как если бы программист явно определил константу и использовал в качестве операнда ее метку. (Действительно, сгенерированный объектный код для строк 45 и 215 на рис.2.10 идентичен объектному коду для соответствующих строк программы на рис.2.6). Вам следует сравнить объектный код, сгенерированный для строк 45 и 55 (рис.2.10), для того, чтобы убедиться, что вы уяснили разницу в обработке литералов и непосредственных операндов.

Все литеральные операнды, используемые в программе, объединяются в один или несколько ЛИТЕРАЛЬНЫХ ПУЛОВ (literals pools). Обычно литеральный пул помещается в конце программы и распечатывается в ее листинге. В литеральном пуле для каждого литерала показаны назначенный ему адрес и сгенерированное значение. Пример такого литерального пула приведен на рис.2.10 (он расположен непосредственно после предложения END). В данном случае пул состоит только из одного литерала =X'05'.

Однако в некоторых случаях хотелось бы размещать литеральный пул в другом месте объектной программы. Это можно сделать с помощью директивы ассемблера LTORG (строка 93 на рис.2.9). Когда ассемблер встречает предложение LTORG, он создает литеральный пул, содержащий все литеральные операнды, которые были использованы в программе с момента обработки предыдущего предложения LTORG (или с начала программы). Литеральный пул помещается в объектную программу в то место, где было встречено предложение LTORG (см. рис.2.10). Конечно, литералы, помещенные в литеральный пул по команде LTORG, не будут повторно помещаться в литеральный пул в конце программы.

Если бы мы не использовали предложение LTORG в строке 93, то литерал =C'EOF' был бы помещен в литеральный пул в конце программы. В этом случае данный операнд получил бы адрес 1073 и мы не смогли бы использовать для него адресацию относительно счетчика команд. Конечно, все дело в том, что мы зарезервировали очень большое пространство для массива BUFFER. Расположив литеральный пул перед этим массивом, мы смогли избежать использования расширенного командного формата для ссылок на литералы. Обычно необходимость в команде, подобной LTORG, возникает тогда, когда желательно расположить литеральные операнды в непосредственной близости от команд, в которых они используются.

Большинство ассемблеров умеют распознавать повторное использование литералов и хранят только один экземпляр каждого из них. Например, литерал =X'05' используется в нашей программе в строках 215 и 230. Однако для его хранения генерируется только одна константа и операнды обеих команд ссылаются на адрес этой константы.

Простейший способ распознать повторное использование литерала заключается в сравнении определяющих их строк символов (в нашем случае это строка =X'05'). Однако в некоторых случаях лучше сравнивать не определения, а сгенерированные коды. Например, литералы =C'EOF' и =X'454F46' определяют идентичные значения. Если бы ассемблер умел распознавать их эквивалентность, то он мог бы не заводить дублирующую константу. В то же время выигрыш от подобного способа обработки литералов обычно не столь велик, чтобы оправдать дополнительные усложнения ассемблера.

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

LDB =*
BASE *

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

Однако такое обозначение может вызвать трудности распознавания повторно используемых литералов. Если литерал =* появился бы в нашей программе в строке 13, то его значение было бы 0003. Если тот же операнд был бы задан в строке 55, то он определял бы операнд, имеющий значение 0020. В этом случае литеральные операнды имеют идентичные имена, но различные значения. Следовательно, оба эти операнда должны быть помещены в литеральный пул. Те же самые проблемы возникают, если литерал ссылается на какой-либо другой объект, значение которого изменяется между двумя точками программы.

Теперь мы готовы к тому, чтобы описать, как ассемблер обрабатывает литеральные операнды. Основной структурой данных, используемой при обработке литералов, является ТАБЛИЦА ЛИТЕРАЛОВ LITTAB. Для каждого использованного в программе литерала эта таблица содержит следующую информацию: имя литерала, его значение, длину и назначенный ему адрес. Очень часто LITTAB организуется в виде хеш-таблицы, в которой в качестве ключа используется имя литерала или его значение.

Если во время первого просмотра ассемблер встречает литеральный операнд, то он пытается найти его в LITTAB. Если литерал уже присутствует в таблице, то никаких дополнительных действий не требуется. Если литерала в таблице еще нет, то он помещается в LITTAB (значение адреса пока остается неопределенным). Когда во время первого просмотра встречается предложение LTORG или конец программы, ассемблер просматривает таблицу литералов и назначает каждому литералу его адрес (за исключением тех литералов, которым адреса были назначены ранее). После назначения адреса для очередного литерала счетчик размещений продвигается на длину литерального операнда.

Во время второго просмотра адреса литеральных операндов извлекаются из LITTAB и используются для генерации объектного кода. Значения литеральных операндов в литеральном пуле заносятся в соответствующие места объектной программы точно так же, как если бы эти значения были сгенерированы с помощью предложений BYTE или WORD. Если значение литеральнего операнда представляет собой программный адрес (например, значение счетчика размещений), то ассемблер должен сгенерировать для него соответствующую запись-модификатор в объектном представлении.

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

2.3.2. СРЕДСТВА ОПРЕДЕЛЕНИЯ ИМЕН

До сих пор в рассмотренных нами ассемблерных программах мы имели дело только с одним способом определения символических имен пользователя. Эти имена появлялись в качестве меток команд или областей данных. Значением такой метки является адрес, назначаемый предложению, в котором она определена. Большинство ассемблеров предоставляет программисту дополнительные средства для определения имен и задания их значений. К числу обычно используемых директив ассемблера относится директива EQU (от EQUate). В общем виде это предложение записывается следующим образом:

имя EQU значение

Данное предложение определяет некоторое имя (т.е. вносит его в SYMTAB) и присваивает ему значение. Значение может задаваться в виде константы или выражения, в котором могут использоваться константы и ранее определенные, имена. Образование и использование выражений мы обсудим в следующем разделе.

Одним из общих применений EQU является введение символических имен вместо числовых значений, чтобы упростить чтение программы. Например, в строке 133 программы на рис.2.5 для того, чтобы загрузить в регистр T число 4096, мы использовали предложение

+LDT #4096

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

MAXLEN EQU 4096

то сможем записать строку 133 как

+LDT #MAXLEN

Когда ассемблер встретит предложение EQU, он занесет MAXLEN в таблицу имен (со значением 4096). Во время трансляции команды LDT ассемблер найдет MAXLEN в SYMTAB и использует его значение в качестве операнда. Получаемый при этом объектный код будет таким же, как и в предыдущей версии, однако исходная программа легче для понимания. Кроме того, если понадобится изменить значение величины, задающей максимальную длину записи, то проще сделать это в одном предложении EQU, чем искать все вхождения 4096 в исходной программе.

Другое общее применение EQU заключается в определении мнемонических имен для регистров. Мы предположили, что наш ассемблер распознает стандартные мнемонические имена регистров - A, X, L и т.д. Однако представим себе, что ассемблер требует использовать для задания регистров их номера (например, в команде RMO). Тогда мы должны были бы писать RMO 0, 1 вместо RMO A, X. В этом случае программист мог бы включить в свою программу следующую последовательность предложений:

A EQU 0
X EQU 1
L EQU 2

В результате обработки этих предложений имена A, X, L, ... были бы занесены в SYMTAB и имели бы значения 0, 1, 2, ... После этого команда, подобная RMO A, X, была бы допустима. Ассемблер, обрабатывая такое предложение, должен был бы просмотреть SYMTAB и при трансляции использовать для имен A и X соответствующие им значения 0 и 1.

На машине, подобной УУМ, задание для регистров пользовательских имен большого смысла не имеет. Гораздо проще использовать символические имена, встроенные в ассемблер. К тому же стандартные имена (base, index и т.п.) отражают способ использования регистров. Рассмотрим, однако, машину, имеющую регистры общего назначения (например, System/370). Обычно для обозначения этих регистров используются номера вида 0, 1, 2, ... или имена вида R0, Rl, R2, .... В конкретной программе некоторые из них могут быть использованы как базовые регистры, другие - как индексные, третьи - как сумматоры и т.д. Более того, в различных программах функциональное распределение регистров может быть различным. В этом случае программист может определить имена, отражающие функциональное назначение регистров конкретной программы, с помощью следующих предложений:

BASE EQU 1
COUNT EQU 2
INDEX EQU 3

Имеется еще одна общеупотребительная директива ассемблера, позволяющая косвенно задавать значения символическим именам. Обычно эта директива называется ORG (от ORiGin)" Она имеет следующий вид:

ORG значение

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

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

Бек. Введение в системное программирование. 1988 8807110

В этой таблице SYMBOL представляет собой 6-байтовое поле для хранения имен; VALUE - 1-словное поле для хранения значения, присвоенного имени; FLAGS - 2-байтовое поле для спецификации типа и другой информации.

Мы могли бы зарезервировать пространство для этой таблицы с помощью предложения

STAB RESB 1100

Естественно, что мы хотим иметь возможность ссылаться на элементы таблицы с помощью индексной адресации (помещая в индексный регистр величину смещения требуемого элемента относительно начала таблицы). Так как нам нужен доступ к отдельным полям элементов, то мы должны определить метки SYMBOL, VALUE и FLAGS. С помощью предложений EQU это можно сделать следующим образом;

SYMBOL EQU STAB
VALUE EQU STAB+6
FLAGS EQU STAB+9

Теперь для ссылки на поле VALUE мы можем использовать предложения вида

LDA VALUE,X

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

С помощью предложения ORG мы можем определить те же имена следующим образом:

STAB RESB 1100
ORG STAB
SYMBOL RESB 6
VALUE RESW 1
FLAGS RESB 2
ORG STAB+1100

Первое предложение ORG заносит в счетчик размещений начальный адрес таблицы STAB. В следующем предложении метке SYMBOL будет присвоено текущее значение LOCCTR (т.е. тот же адрес, что и STAB). Затем LOCCTR будет продвинут и его новое значение (STAB+6) будет присвоено метке VALUE. Аналогично будет определено значение метки FLAGS. В результате эти метки получат такие же значения, как и при определении с помощью EQU, но теперь структура STAB стала очевидной.

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

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

ALPHA RESW 1
BETA EQU ALPHA

является допустимой, тогда как последовательность

BETA EQU ALPHA
ALPHA RESW 1

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

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

ORG ALPHA
ВYТЕ1 RESB 1
BYTE2 RESB 1
BYTE3 RESB 1
ORG
ALPHA RESW 1

не может быть обработана. В данном случае ассемблер не знает (во время первого просмотра), какое значение следует установить в счетчик размещений в ответ на первое предложение ORG. В результате невозможно во время первого просмотра вычислить адреса для меток BYTE1, BYTE2, BYTE3.

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

ALPHA EQU BETA
BETA EQU DELTA
DELTA RESW 1

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

2.3.3. ВЫРАЖЕНИЯ

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

Обычно допускаются арифметические выражения, которые строятся по стандартным правилам с помощью операций +, -, *, /. Деление чаще всего определяется как целочисленное. В выражениях можно использовать константы, метки и специальные термы. Одним из таких общеупотребительных специальных термов является терм, ссылающийся на текущую величину счетчика размещений (часто обозначается как *). Значением этого терма является адрес, который будет присвоен очередной команде или области данных. Так, на рис.2.9 предложение

106 BUFEND EQU *

устанавливает в качестве значения BUFEND адрес байта, расположенного непосредственно после поля, отведенного под буфер.

В разд.2.2 мы обсуждали проблемы, связанные с перемещением программ. Мы видели, что в объектной программе имеются адреса двух типов: ОТНОСИТЕЛЬНЫЕ (их значения могут быть вычислены суммированием начального адреса программы с некоторым постоянным смещением) и АБСОЛЮТНЫЕ (не зависящие от начального адреса программы). Аналогично могут быть относительными или абсолютными термы и выражения. Константа - абсолютный терм. Метки команд и областей данных, а также ссылка на текущее значение счетчика размещений - относительные термы. Имена, определенные в предложениях EQU (или других подобных директивах ассемблера), могут быть как абсолютными, так и относительными в зависимости от выражений, определяющих их значения.

Выражения классифицируются как АБСОЛЮТНЫЕ или ОТНОСИТЕЛЬНЫЕ в зависимости от того, к какому типу относится его значение. Естественно, что выражение, состоящее только из абсолютных термов, является абсолютным выражением. Однако абсолютное выражение может содержать и относительные термы. Для этого необходимо, чтобы относительные термы образовывали пары, причем в каждую пару должен входить как терм со знаком плюс, так и терм со знаком минус. Совершенно необязательно, чтобы парные термы располагались друг за другом. Единственное требование - иметь возможность так преобразовать выражение, чтобы все термы могли быть сгруппированы в пары, в каждую из которых входит как положительный, так и отрицательный терм. Относительные термы не могут быть использованы в операциях умножения и деления.

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

Хотя приведенные выше правила могут показаться произвольными, они на самом деле имеют глубокий смысл. К числу выражений, удовлетворяющих данным определениям, относятся только те выражения, значения которых не теряюг смысла при перемещении программ. Относительный терм или выражение представляет собой некоторое значение, которое может быть записано в виде (S+r), где S - начальный адрес программы, а r - величина смещения относительно начального адреса. Поэтому обычно относительный терм задает некоторую точку внутри программы. Когда относительные термы комбинируются в пары, каждая из которых включает термы с противоположными знаками, величины S, определяющие начальный адрес программы, взаимно исключаются. В результате получается абсолютное значение.

Рассмотрим, например, программу на рис.2.9. В выражении

107 MAXLEN EQU BUFEND-BUFFER

обе метки BUFEND и BUFFER - относительные термы, каждый из которых определяет внутренний адрес программы. Однако само выражение (РАЗНОСТЬ между двумя адресами) является величиной абсолютной и определяет длину в байтах области данных, отведенной под буфер.

Значения таких выражений, как BUFEND+BUFFER, 100-BUFFER или 3*BUFFER, не представляют ни абсолютную величину, ни внутренний адрес программы. Зависимость этих значений от адреса начальной загрузки такова, что она не имеет связи с чем-либо в самой программе. Поскольку маловероятно, чтобы от таких выражений была какая-либо польза, они рассматриваются как ошибочные.

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

Бек. Введение в системное программирование. 1988 8807510

ИМЯ. ТИП. ЗНАЧЕНИЕ
RETADR. R. 0030
BUFFER. R. 0036
BUFEND. R. 1036
MAXLEN. A. 1000

С помощью этой информации ассемблер может легко определить тип каждого выражения и сгенерировать в объектной программе записи-модификаторы для относительных выражений.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Сб Фев 06, 2021 12:00 pm

2.3.4. ПРОГРАММНЫЕ БЛОКИ

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

Многие ассемблеры предоставляют более гибкие средства обработки исходной программы и соответствующего ей объектного кода. Одни из этих средств позволяют располагать сгенерированные машинные команды и данные в объектной программе в порядке, отличном от порядка, в котором расположены соответствующие им исходные предложения. Другие позволяют создавать несколько независимых частей объектной программы. Каждая из этих частей сохраняет свою индивидуальность и обрабатывается загрузчиком отдельно от других частей. Мы используем термин ПРОГРАММНЫЕ БЛОКИ (program blocks) для обозначения сегментов, расположенных в пределах одного объектного модуля, и термин УПРАВЛЯЮЩИЕ СЕКЦИИ (control sections) для обозначения сегментов, которые транслируются в независимые объектные модули. (Данная терминология, к сожалению, не является общепризнанной. Фактически в некоторых системах одни и те же средства языка ассемблера используются для обеспечения обеих этих логически различных функций). В этом разделе мы рассмотрим как используются и обрабатываются ассемблером программные блоки. Разд.2.3.5 посвящен обсуждению управляющих секций.

На рис.2.11 показано, как с помощью программных блоков может быть записана наша программа. В данном случае используются три блока. Первый (непоименованный) программный блок содержит выполняемые команды. Второй (с именем CDATA) содержит все области данных, длина которых не превышает нескольких слов. Третий (с именем CBLKS) состоит из областей данных, занимающих значительный объем оперативной памяти. Некоторые из возможных доводов в пользу подобного разделения обсуждаются в данном разделе.

Бек. Введение в системное программирование. 1988 8807710
Рис.2.11. Пример программы с несколькими программными блоками.

Директива ассемблера USE указывает на то, какие части исходной программы принадлежат к тем или иным блокам. Вначале предполагается, что все предложения исходной программы являются частью непоименованного (задаваемого по умолчанию) блока. Если не использовано ни одного предложения USE, то считается, что вся программа принадлежит одному этому блоку. Предложение USE в строке 92 сигнализирует о начале блока с именем CDATA. К этому блоку относятся все последующие предложения исходной программы вплоть да следующего предложения USE в строке 103, которое начинает блок с именем CBLKS. Предложение USE может также указывать на продолжение ранее начатого блока. Так, предложение в строке 123 продолжает непоименованный блок, а предложение в строке 183 - блок CDATA.

Как вы видите, каждый блок фактически может содержать несколько отдельных сегментов исходной программы. Ассемблер перегруппирует (логически) эти сегменты так, чтобы собрать вместе отдельные части каждого блока. Затем каждому из этих блоков будут назначены адреса в объектной программе в порядке появления блоков в исходной программе. Результат будет в точности таким же, как если бы программист собственноручно перегруппировал предложения исходной программ мы, собрав вместе предложения, принадлежащие каждому из блоков.

Такая логическая перегруппировка осуществляется ассемблером во время первого просмотра. Для этого с каждым программным блоком связывается свой счетчик размещений. При объявлении нового блока связанный с ним счетчик размещений устанавливается в 0. Текущее значение счетчика размещений сохраняется при переключении на другой блок и восстанавливается, когда возобновляется ранее определенный блок. Таким образом, во время первого просмотра каждая метка программмы получает адрес относительно начала содержащего ее блока. Когда метка заносится в таблицу имен, то вместе с присвоенным ей адресом заносится имя или номер блока, которому; она принадлежит. По завершению первого просмотра значение счетчика размещений каждого блока указывает на длину этого блока. Затем ассемблер может назначить каждому блоку начальный адрес в объектной программе (начиная с относительного адреса 0).

Во время второго просмотра для генерации объектного кода ассемблеру для каждого имени нужно знать значение адреса относительно начала объектной программы (а не относительно начала блока), Эту информацию можно легко получить из SYMTAB: достаточно просто прибавить к адресу метки начальный адрес ее блока.

На рис.2.12 показано, как этот процесс применяется к нашей модельной программе. В столбце "Адрес" приведены относительный адрес (внутри блока) каждого предложения и номер соответствующего ему блока (0 = непоименованный блок, 1 = CDATA, 2 = CBLKS). По существу это та же информация, которая хранится в SYMTAB для каждого имени. Обратите внимание, что для значения MAXLEN (строка 107) номер блока не указан. Это означает, что MAXLEN является абсолютной меткой, значение которой не связано ни с одним из блоков.

Бек. Введение в системное программирование. 1988 8807910
Рис.2.12. Объектный код для программы на рис.2.11.

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

Бек. Введение в системное программирование. 1988 8808010

Имя блока. Номер блока. Адрес. Длина
(непоименованный). 0. 0000. 0066
CDATA. 1. 0066. 000B
CBLKS. 2. 0071. 1000

Рассмотрим теперь команду

20 0006 0 LDA LENGTH 032060

В SYMTAB значение ее операнда (метка LENGTH) определено как относительный адрес 0003, принадлежащий программному блоку 1 (CDATA). Начальный адрес CDATA равен 0066. Таким образом, требуемый целевой адрес для этой команды будет 0003+0066 = 0069. Команда должна ассемблироваться с использованием адресации относительно счетчика команд. Во время исполнения данной команды счетчик команд будет содержать адрес следующей команды (строка 25). Относительный адрес этой команды равен 0009 и принадлежит непоименованному блоку. Поскольку этот блок начинается с адреса 0000, то, следовательно, значение этого адреса будет просто равно 0009. Итак, требуемое смещение вычисляется: 0069-0009 = 60. Вычисление других адресов осуществляется аналогично.

Сразу видно, что разделение программы на блоки существенно уменьшило проблемы адресации. Так как теперь область памяти, отведенная под буфер большого объема, переместилась в конец программы, то отпала необходимость в использовании расширенного командного формата в строках 15, 35, 65. Более того, отпала надобность в базовом регистре, и мы можем исключить предложения LDB и BASE, которые ранее были в строках 13 и 14. Проблема размещения литералов (и организации ссылок на них) также решается намного проще. Мы просто включили предложение LTORG в блок CDATA для того, чтобы быть уверенными, что литералы будут расположены впереди всех областей данных, занимающих значительный объем памяти.

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

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

Для того чтобы разместить ФРАГМЕНТЫ каждого программного блока рядом, совершенно необязательно выполнять физическую реорганизацию сгенерированного объектного кода. Ассемблер может просто записывать объектный код по мере его генерации во время второго просмотра и вставлять соответствующий адрес загрузки в каждую ЗАПИСЬ ТЕЛА ПРОГРАММЫ. Эти адреса загрузки будут, конечно, отражать наряду с относительным расположением команд и данных внутри блока также и начальный адрес самого блока. Иллюстрация этому дана на рис.2.13. Две первые записи тела программы генерируются из строк исходной программы с номерами с 5 до 70. Когда в строке 92 встречается предложение USE, ассемблер записывает текущую запись тела программы (хотя в ней еще есть свободное место). Затем ассемблер подготавливается к открытию новой записи тела программы для нового программного блока. Однако ввиду того, что предложения в строках с 95 по 105 не приводят к генерации объектного кода, не создается и соответствующих им записей тела программы. Следующие две записи тела программы получаются из строк 125-180. На этот раз предложения, принадлежащие очередному программному блоку, действительно требуют генерации объектного кода. Пятая запись тела программы содержит один-единственный байт данных, генерируемый из строки 185. Шестая запись тела программы возобновляет непоименованный блок, и оставшаяся часть объектной программы обрабатывается таким же образом.

Бек. Введение в системное программирование. 1988 8808110
Рис.2.13. Объектная программа, соответствующая рис.2.11.

То, что записи тела программы расположены в объектной программе не в порядке возрастания адресов, не имеет значения. Загрузчик просто помещает каждую запись по указанному адресу. По завершению загрузки сгенерированный код непоименованного блока будет занимать относительные адреса с 0000 по 0065; сгенерированный код и зарезервированные области данных блока CDATA будут занимать адреса с 0066 по 0070, а память, зарезервированная для CBLKS, будет занимать адреса с 0071 по 1070. На рис.2.14 прослежен процесс ассемблирования и загрузки блоков нашей модельной программы. Обратите внимание на то, что предложения программы, обозначенные CDATA(1) и CBLKS(1), фактически не присутствуют в объектной программе. Резервирование памяти для этих областей обеспечивается автоматически при загрузке программы с помощью механизма назначения адресов.

Бек. Введение в системное программирование. 1988 8808210
Рис.2.14. Программные блоки для рис.2.11
после выполнения ассемблирования и загрузки.

Для того чтобы убедиться, что вы поняли, как ассемблер обрабатывает программы, состоящие из нескольких блоков, вам следует тщательно изучить сгенерированный код (рис.2.12) и выполнить ассемблирование еще нескольких команд. А чтобы уяснить, как различные части каждого программного блока собираются вместе, вам следует также воспроизвести (вручную) загрузку объектной программы, приведенной на рис.2.13.

2.3.5. УПРАВЛЯЮЩИЕ СЕКЦИИ И СВЯЗЫВАНИЕ ПРОГРАММ

В этом разделе мы обсудим, как обрабатываются программы, состоящие из нескольких управляющих секций. УПРАВЛЯЮЩАЯ СЕКЦИЯ - это часть программы, которая после ассемблирования сохраняет свою индивидуальность и может загружаться и перемещаться независимо от других. В отдельные управляющие секции чаще всего выделяются подпрограммы или другие логические подразделы программы. Программист может отдельно ассемблировать и загружать каждую из таких управляющих секций. Достигаемая за счет этого гибкость является основным преимуществом их использования. Примеры этого будут рассмотрены в гл.3, когда мы будем обсуждать редакторы связей.

Поскольку управляющие секции образуют логически связанные части программы, то, следовательно, необходимо предоставить некоторые средства для их СВЯЗЫВАНИЯ (linking) друг с другом. Например, команды одной управляющей секции должны иметь возможность ссылаться на команды или области данных, расположенные в другой секции. Такие ссылки нельзя обрабатывать обычным образом, поскольку управляющие секции загружаются и перемещаются независимо друг от друга и ассемблеру ничего не известно о том, где будут расположены другие управляющие секции во время исполнения программы, Такие ссылки между управляющими секциями называются ВНЕШНИМИ ССЫЛКАМИ (external references). Для каждой внешней ссылки ассемблер генерирует информацию, которая дает возможность загрузчику выполнить требуемое связывание программ. В этом разделе мы опишем, как наш ассемблер обрабатывает внешние ссылки. Реальное выполнение связывания будет детально обсуждаться в гл.3.

На рис.2.15 показано, как наша модельная программа может быть записана в виде нескольких управляющих секций. В данном случае имеются три управляющие секции: одна для главной программы и по одной для каждой из подпрограмм. Предложение START определяет начало ассемблируемой программы и задает имя (COPY) первой управляющей секции. Первая секция расположена вплоть до предложения CSECT в строке 109. Эта директива ассемблера сигнализирует о начале новой управляющей секции с именем RDREC. Аналогично предложение CSECT в строке 193 начинает управляющую секцию с именем WRREC. Для каждой управляющей секции ассемблер заводит отдельный счетчик размещений (начинающийся с 0) точно так же, как он это делает для программных блоков.

Бек. Введение в системное программирование. 1988 8808510
Рис.2.15. Иллюстрация управляющих секций и связывания программы.

От программных блоков управляющие секции отличаются тем, что они обрабатываются ассемблером независимо друг от друга. (Необязательно даже, чтобы все управляющие секции программы ассемблировались одновременно). Имена, определенные в одной управляющей секции, не могут непосредственно использоваться в другой секции. Для того чтобы загрузчик смог их обработать, они должны быть описаны как внешние ссылки. На рис.2.15 показаны две директивы ассемблера для задания таких ссылок: EXTDEF (определение внешних имен) и EXTREF (объявление внешних ссылок). Предложение EXTDEF определяет имена, описанные в данной управляющей секции, но доступные для использования в других секциях. Такие имена называются ВНЕШНИМИ ИМЕНАМИ (external symbols). Имена управляющих секций (в данном случае COPY, RDREC и WRREC) определять в предложении EXTDEF не требуется, так как они автоматически считаются внешними. Предложение EXTREF объявляет имена, которые используются в данной управляющей секции, но определены где-либо еще. Например, имена BUFFER, BUFEND и LENGTH, определенные в управляющей секции с именем COPY, делаются доступными другим секциям с помощью предложения EXTDEF в строке 6. В третьей управляющей секции (WRREC), согласно предложению EXTREF (строка 207), используются два внешних имени. Порядок перечисления имен в предложениях EXTDEF и EXTREF несуществен.

Бек. Введение в системное программирование. 1988 8808610
Рис.2.16. Объектный код для программы на рис.2.15.

Теперь мы готовы к тому, чтобы посмотреть, как ассемблер обрабатывает внешние ссылки. На рис.2.16 для каждого предложения программы показан сгенерированный объектный код. Для начала рассмотрим команду

15 0003 CLOOP +JSUB RDREC 4B100000

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

160 0017 +STCH BUFFER,X 57900000

имеется внешняя ссылка на метку BUFFER. Эта команда ассемблируется с использованием расширенного командного формата и нулевым адресом. Разряд x установлен в 1, что является признаком индексной адресации. Предложение

190 0028 MAXLEN WORD BUFEND-BUFFER 000000

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

Укажем на различие в обработке выражения в строке 190 и похожего выражения в строке 107. Имена BUFEND и BUFFER определены в той же управляющей секции, что и предложение EQU в строке 107. Поэтому значение этого выражения может быть вычислено непосредственно ассемблером. Для строки 190 это сделать нельзя, так как BUFEND и BUFFER определены в другой управляющей секции и, следовательно, во время ассемблирования их значения неизвестны.

Как видно из предыдущих рассуждений, ассемблер должен запоминать (в SYMTAB), в какой управляющей секции было определено то или иное имя. Любая попытка использовать имя из другой управляющей секции должна отмечаться как ошибка, если только это имя не объявлено (с помощью EXTREF) в качестве внешнего. Ассемблер должен разрешать использовать одинаковые имена в разных управляющих секциях. Например, противоречивые определения MAXLEN в строках 107 и 190 не должны вызвать никаких проблем. Ссылки на MAXLEN в управляющей секции COPY будут использовать определение в строке 107, тогда как ссылки в RDREC - определение в строке 190.

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

Два новых типа записей - это запись-определение и запись-ссылка. Запись-определение содержит информацию о внешних именах, определенных в данной управляющей секции, т.е. именах, объявленных в EXTDEF. В записи-ссылке содержится список имен, используемых в данной управляющей секции в качестве внешних ссылок, т.е. имен, объявленных в EXTREF. Ниже приводятся форматы этих записей.

Запись-определение:
Столбец 1 - D.
Столбцы 2-7 - Идентификатор внешнего имени, определенный в данной управляющей секции.
Столбцы 8-13 - Относительный адрес имени (шестнадцатеричный).
Столбцы 14-73 - Информация, аналогичная столбцам 2-13 - для других внешних имен.

Запись-ссылка:
Столбец 1 - R.
Столбцы 2-7 - Идентификатор внешнего имени, на который есть ссылка в данной управляющей секции.
Столбцы 8-73 - Информация, аналогичная столбцам 2-7 - для других внешних имен.

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

Запись-модификатор:
Столбец 1 - M.
Столбцы 2-7 - Начальный адрес модифицируемого адресного поля относительно начала управляющей секции (шестнадцатеричный).
Столбцы 8-9 - Длина модифицируемого адресного поля в полубайтах (шестнадцатеричная).
Столбец 10 - Признак модификации (+ или -).
Столбцы 11-16 - Внешнее имя, значение которого надо добавить к заданному полю или вычесть из него.

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

На рис.2.17 показана объектная программа, соответствующая исходному тексту рис.2.16. Обратите внимание, что для каждой управляющей секции имеется отдельный набор записей объектной программы (от записи-заголовка до записи-конца). Записи каждой управляющей секции в точности такие же, как если бы эти секции ассемблировались раздельно.

Бек. Введение в системное программирование. 1988 8809010
Рис.2.17. Объектная программа, соответствующая рис.2.15.

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

Давайте теперь изучим процесс связывания внешних ссылок, начиная с ранее рассмотренных исходных предложений. Поле адреса команды JSUB в строке 15 начинается с относительного адреса 0004. В объектной программе его начальное значение равно 0. Запись-модификатор

M00000405+RDREC

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

Обработка 1-словной константы, сгенерированной по строке 190, мало чем отличается от ранее описанного процесса. Значением этой константы является выражение BUFEND-BUFFER, где обе метки определены в другой управляющей секции. Ассемблер генерирует эту константу (расположенную по относительному адресу 0028 в управляющей секции RDREC) с нулевым начальным значением. Две последние записи-модификаторы в RDREC требуют, чтобы адрес BUFEND был добавлен к указанному полю, а адрес BUFFER был вычтен. Эти вычисления выполняются во время загрузки, и в результате получается требуемая константа.

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

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

M00000705
M00001405
M00002705

будут заменены на записи-модификаторы вида

M00000705+COPY
M00001405+COPY
M00002705+COPY

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

Наличие нескольких управляющих секций, каждая из которых может перемещаться независимо от других, создает дополнительные трудности при обработке выражений. Ранее мы требовали, чтобы все относительные термы выражения группировались в пары (для абсолютного выражения) или чтобы все относительные термы, кроме одного, группировались в пары (для относительного выражения). Теперь мы вынуждены усилить эти ограничения и потребовать, чтобы парные термы принадлежали одной управляющей секции. Причина этого проста - если оба терма определяют относительные метки в одной и той же управляющей секции, то их разность является абсолютной величиной (независимо от того, где будет расположена управляющая секция). С другой стороны, если метки относятся к различным управляющим секциям, то значение их разности заранее непредсказуемо (и потому, вероятно, бесполезно). Например, выражение BUFEND-BUFFER определяет длину буфера в байтах. С другой стороны, выражение RDREC - COPY определяет разность между адресами загрузки двух управляющих секций. Эта величина зависит от способа размещения программы в оперативной памяти, и маловероятно, чтобы она была полезна в прикладных программах.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вс Фев 07, 2021 12:10 pm

2.4. ВАРИАНТЫ ПОСТРОЕНИЯ АССЕМБЛЕРОВ

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

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

2.4.1. ДВУХПРОСМОТРОВЫЙ АССЕМБЛЕР С ОВЕРЛЕЙНОЙ СТРУКТУРОЙ

Как мы видели, в большинстве ассемблеров процесс обработки исходной программы делится на два просмотра. Внутренние таблицы и подпрограммы, которые используются только во время первого просмотра, становятся ненужными после его завершения. Многие подпрограммы и таблицы используются только для одного просмотра и никогда не требуются для другого. В то же время некоторые таблицы (например, SYMTAB) и подпрограммы (например, поиск в SYMTAB) используются в обоих просмотрах.

На рис.2.18 представлена общая структура двухпросмотрового ассемблера. Этот ассемблер состоит из трех сегментов, образующих дерево. Корневой сегмент содержит простую управляющую программу, функцией которой является поочередный вызов двух других сегментов (Просмотр 1 и Просмотр 2). Корневой сегмент содержит таблицы и подпрограммы, необходимые для обоих просмотров,

Бек. Введение в системное программирование. 1988 8809310
Рис.2.18. Структура двухпросмотрового ассемблера.

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

Бек. Введение в системное программирование. 1988 88093a10
Рис.2.19. Двухпросмотровый ассемблер с оверлейной структурой.

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

2.4.2. ОДНОПРОСМОТРОВЫЕ АССЕМБЛЕРЫ

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

Довольно легко исключить ссылки вперед на данные; достаточно потребовать, чтобы все области данных определялись в исходной программе раньше, чем появляются команды, которые на них ссылаются. Это не слишком жесткое ограничение. Программист просто размещает все области данных в начале программы, а не в конце. К несчастью, невозможно столь же легко исключить ссылки вперед на метки команд. Логика программы часто требует передачи управления вперед. (Например, выход из цикла после проверки некоторого условия.) Требование исключить все такие передачи управления оказалось бы гораздо более жестким и неудобным. Поэтому ассемблер должен предпринимать специальные меры для обработки ссылок вперед. Однако для того, чтобы облегчить задачу, многие однопросмотровые ассемблеры действительно запрещают (или по крайней мере не рекомендуют) ссылки вперед на данные.

Существует два основных типа однопросмотровых ассемблеров. Ассемблеры первого типа записывают объектный код непосредственно в оперативную память для немедленного исполнения; ассемблеры второго типа создают объектную программу, которая будет выполняться позднее. Обсуждение обоих типов мы проиллюстрируем с помощью программы на рис.2.20. Это тот же самый пример, что и на рис.2.2, но все определения данных расположены перед командами, в которых они используются. Сгенерированный объектный код показан на рис.2.20 только для справки, Мы обсудим, как однолросмотровый ассемблер каждого типа мог бы в действительности сгенерировать требуемую объектную программу.

Бек. Введение в системное программирование. 1988 8809510
Рис.2.20. Модельная программа для однопросмотрового ассемблера.

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

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

Рассмотрим пример, который поможет прояснить этот процесс. На рис.2.21а показано, как будут выглядеть объектный код и элементы таблицы имен после просмотра строки 40 программы на рис. 2.20. Первая ссылка вперед была в строке 15. Поскольку операнд (RDREC) еще не был определен, то команда ассемблировалась без назначения адреса для этого операнда (на рисунке обозначено как ----). Затем имя RDREC было занесено в SYMTAB как неопределенное имя (обозначено *), а адрес операндного поля команды (2013) был занесен в список, связанный с RDREC. Аналогично обрабатывались команды в строках 30 и 35.

Бек. Введение в системное программирование. 1988 8809710
Рис.2.21. Объектные коды, записанные в оперативную память, и элементы таблицы имен после просмотра строки 40 (а) и строки 160 (б) программы на рис. 220.

Рассмотрим теперь рис.2.21б, соответствующий ситуации после просмотра строки 160. К этому моменту была разрешена часть ссылок вперед, в то время как другие были добавлены. Когда было определено имя ENDFIL (строка 45), ассемблер занес его значение в SYMTAB. Затем он занес это значение в операндное поле команды (по адресу 201C), как это предписано списком ссылок вперед. С этого момента все ссылки на ENDFIL не будут являться ссылками вперед и не будут заноситься в список. Аналогично определение RDREC (строка 125) позволило заполнить операндное поле по адресу 2013. Тем временем были добавлены две новые ссылки вперед: WRREC (строка 65) и EXIT (строка 155). Вам следует продолжить этот процесс до конца программы и самостоятельно убедиться в том, что все ссылки вперед будут занесены правильно. Когда будет обнаружен конец программы, все элементы SYMTAB с признаком * будут указывать на неопределенные имена, и, следовательно, ассемблер должен отметить их как ошибки.

По достижению конца программы ассемблирование завершается. Если не было ошибок, то ассемблер ищет в SYMTAB значение имени, указанное в предложении END (в данном случае FIRST), и передает управление на данный адрес для исполнения ассемблированной программы.

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

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

Иллюстрация этого процесса дана на рис.2.22. Вторая запись тела программы содержит код, сгенерированный для строк 10-40 исходной программы, изображенной на рис.2.20. Адреса операндов команд для строк 15, 30 и 35 сгенерированы с нулевым значением. Когда в строке 45 встречается определение имени ENDFIL, ассемблер генерирует третью запись тела программы. Эта запись определяет, что значение 2024 (адрес ENDFIL) должно быть занесено по адресу 201C (адресное поле операнда команды JEQ в строке 30). Таким образом, во время загрузки программы величина 2024 заменит ранее занесенное значение 0000. Точно так же обрабатываются и другие ссылки вперед нашей программы. В результате для разрешения ссылок вперед, которые не могут быть обработаны ассемблером, используются средства загрузчика. Конечно, при передаче объектной программы загрузчику должен быть сохранен первоначальный порядок ее записей.

Бек. Введение в системное программирование. 1988 8809910
Рис. 2.22. Объектная программа, полученная из исходной программы на рис.2.20 с помощью однопросмотрового ассемблера.

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

2.4.3. МНОГОПРОСМОТРОВЫЕ АССЕМБЛЕРЫ

При обсуждении директивы ассемблера EQU мы требовали, чтобы все имена в правой части (т.е. в выражении, задающем значение нового имени) были предварительно определены в исходной программе. Аналогичные ограничения накладывались и на директиву ассемблера ORG. В действительности такие ограничения обычно накладываются на все директивы ассемблера, которые (прямо или косвенно) определяют имена.

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

ALPHA EQU BETA
BETA EQU DELTA
DELTA EQU 1

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

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

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

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

Бек. Введение в системное программирование. 1988 8810111 Бек. Введение в системное программирование. 1988 8810210
Рис.2.23. Пример работы многопросмотрового ассемблера.

На рис.2.23б приведены элементы таблицы имен, получившиеся после первого просмотра предложения

HALFSZ EQU MAXLEN/2

Имя MAXLEN еще не определено, поэтому невозможно вычислить значение HALFSZ. Вместо значения HALFSZ в таблице имен запоминается определяющее его выражение. Элемент &1 показывает, что в определяющем выражении не определено одно имя. Конечно, в реальной реализации это определение может храниться где-нибудь в другом месте. SYMTAB может просто содержать указатель на определяющее выражение. Имя MAXLEN также занесено в таблицу имен и снабжено признаком *, указывающим на то, что оно не определено. С этим именем связан список имен, значения которых зависят ох MAXLEN; в данном случае HALFSZ. Обратите внимание на схожесть со способом обработки ссылок вперед в однопросмотровом ассемблере.

Аналогично обрабатывается определение MAXLEN (рис.2.23в). В данном случае в состав определения входят два неопределенных имени: BUFEND и BUFFER. Оба этих имени заносятся в SYMTAB вместе со списком, указывающим на зависимость MAXLEN от этих имен. Точно так же при обработке определения PREVBT потребуется занесение этого имени в список имен, зависящих от BUFFER (рис.2.23г).

До сих пор мы просто запоминали определения имен для последующей обработки. Определение BUFFER в строке 4 позволяет нам начать вычисление некоторых из них. Предположим, что, когда мы читаем строку 4, счетчик размещений содержит шестнадцатеричное значение 1034. Этот адрес запоминается в качестве значения BUFFER. Затем ассемблер просматривает список имен, зависящих от значения BUFFER. Элемент таблицы имен для первого имени данного списка (MAXLEN) показывает, что он зависит от двух имен, которые к данному моменту не определены. Поэтому сразу вычислить значение MAXLEN нельзя. Вместо этого &2 заменяется на &1 для того, чтобы показать, что теперь только одно имя в определении (BUFEND) осталось неопределенным, Другое имя списка (PREVBT) можно вычислить, так как оно зависит только от BUFFER. Выражение, определяющее значение PREVBT, вычисляется и запоминается в SYMTAB, Результат показан на рис.2.23д.

Дальнейшая обработка осуществляется аналогично. Когда в строке 5 определяется BUFEND, его значение заносится в таблицу имен. Список, связанный с BUFEND, предписывает ассемблеру вычислить MAXLEN, а занесение значения MAXLEN вызывает, в свою очередь, вычисление имен из связанного с ним списка (HALFSZ). Как показано на рис.2.23е, на этом процесс определения имен заканчивается. Если какие-либо имена остаются неопределенными, то они отмечаются как ошибочные.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вс Фев 07, 2021 12:25 pm

2.5. ПРИМЕРЫ РЕАЛИЗАЦИИ

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

Мы обсудим примеры ассемблеров для System/370, VAX и CYBER. Прежде чем продолжить чтение данного раздела, вам следует посмотреть в гл.1 описания этих машин.

2.5.1. АССЕМБЛЕР SYSTEM/370

Относительная базовая адресация в System/370 используется с той же целью, что и в УУМ/ДС,- для экономии места, занимаемого машинной командой. Полный адрес System/370 занимает 24 разряда. Вместе с тем тот же самый адрес можно закодировать с помощью номера базового регистра (4 разряда) и смещения (12 разрядов). Однако есть и отличие: в System/370 нет ни адресации относительно счетчика команд, ни чего-либо похожего на расширенный командный формат УУМ/ДС. Поэтому все команды, ссылающиеся на оперативную память, ДОЛЖНЫ использовать относительную базовую адресацию. Как следствие этого при перемещении объектной программы System/370 модификация требуется только для элементов данных, значениями которых являются фактические адреса (т.е. для адресных констант).

В System/370 любой регистр общего назначения (за исключением R0) может использоваться в качестве базового регистра. Решение о том, какие регистры использовать для этой цели, возлагается на программиста. В больших программах обычно одновременно используются несколько различных базовых регистров. Какие регистры могут использоваться в качестве базовых и каково их содержимое, программист сообщает при помощи директивы ассемблера USING. По своим функциям она похожа на директиву BASE языка ассемблера УУМ/ДС. Таким образом, предложения

USING LENGTH,R1
USING BUFFER,R4

определяют в качестве базовых регистры R1 и R4. Предполагается, что R1 будет содержать адрес LENGTH, a R4 - адрес BUFFER. Так же как и в УУМ/ДС, программист должен предусмотреть команды, обеспечивающие загрузку этих регистров во время выполнения программы. Дополнительные предложения USING можно помещать в любом месте программы. Если базовый регистр в дальнейшем необходимо использовать для других целей, то программист с помощью предложения DROP может сообщить ассемблеру, что дальнейшее использование регистра в качестве базового запрещено.

Дополнительная гибкость в использовании регистров влечет за собой увеличение объема работы, выполняемой ассемблером. Для того чтобы знать, какие регистры общего назначения могут в данный момент использоваться в качестве базовых регистров и каково их значение, используется таблица базовых регистров. При выполнении предложения USING в таблицу заносится новый элемент (или модифицируется уже существующий); при выполнении предложения DROP соответствующий элемент удаляется из таблицы. Для каждой команды, операнд которой является адресом оперативной памяти, ассемблер ищет в таблице подходящий для адресации базовый регистр. Если для адресации подходит более одного регистра, то выбирается тот, который дает наименьшую величину смещения. Если ни один из регистров не подходит, то ассемблирование команды невозможно. Процесс вычисления смещения в точности такой же, как и для УУМ/ДС.

Наряду с этим в языке ассемблера System/370 разрешено явное задание базового регистра и смещения непосредственно в исходном предложении. Например, в предложении

L R2,8(R4)

в качестве адреса операнда задан адрес смещенный на 8 байт относительно адреса, содержащегося в R4. Такая форма адресации может быть полезна, если известно, что некоторый регистр содержит начальный адрес таблицы или записи данных, а программист хотел бы сослаться на определенную область внутри этой таблицы или записи. При обработке команд, в которых явно заданы базовый регистр и смещение, ассемблер просто заносит заданные значения в объектный код (в данном случае базовый регистр R4 и смещение 8 ). Таблица базовых регистров в этом случае не требуется; поэтому регистр, используемый подобным образом, не нужно описывать в предложении USING.

Другой проблемой, которая возникает в ассемблере System/370, является ВЫРАВНИВАНИЕ областей данных и команд. Некоторые команды выполняются более эффективно, если их операнды выравнены по границам памяти, соответствующим их длинам. Например, операнд длиной в одно слово (32 разряда) должен начинаться с байта, адрес которого кратен 4, а операнд длиной в полслова (16 разрядов) - с адреса, кратного 2. Точно так же требуется, чтобы машинная команда начиналась с байта, адрес которого кратен 2.

Заботу о таком выравнивании берет на себя ассемблер, продвигая, если это необходимо, счетчик размещений. Предположим, например, что счетчик размещений содержит 0015 и необходимо разместить элемент данных длиной в одно слово. В этом случае, прежде чем резервировать память для этого слова, счетчик размещений следует увеличить до 0018 (ближайшее кратное 4 больше чем 0015). Байты объектной программы, пропущенные для того, чтобы обеспечить соответствующее выравнивание, называются ПАССИВНЫМИ БАЙТАМИ (slack bytes). (В некоторых ассемблерах System/370 включение или выключение выравнивания задается по выбору во время ассемблирования).

Пассивные байты являются для объектной программы потерянной памятью. Величина этих потерь существенно зависит от того, в каком порядке определяются элементы данных. Рассмотрим, например, последовательность предложений резервирования памяти, изображенную на рис.2.24а. Если при обработке первого предложения счетчик размещений содержит, как это показано, адрес, кратный 4, то для размещения с выравниванием потребуется 20 байт памяти. Так как сами данные занимают только 14 байт, то это означает, что внесение пассивных байтов увеличило объем занимаемой оперативной памяти почти на 50%. Если сгруппировать элементы данных, требующие одинакового выравнивания, то можно значительно сократить эти накладные расходы. Например, если те же определения расположить в порядке, показанном на рис.2.246, то не потребуется ни одного пассивного байта.

Бек. Введение в системное программирование. 1988 8810710
Рис.2.24. Влияние выравнивания данных на требуемый объем оперативной памяти.

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

Ассемблер System/370 обеспечивает поддержку аппарата управляющих секций (их называют CSECT). По своим функциям этот аппарат похож на средства, описанные в разд.2.3.5. В то же время не предусмотрено никакого механизма, прямо соответствующего программным блокам (см. разд.2.3.4), В System/370 управляющие секции могут состоять из нескольких отдельных сегментов. Сборка этих сегментов осуществляется ассемблером примерно так же, как это было описано для программных блоков. Однако управляющие секции не образуют единый программный модуль. Каждая из них остается самостоятельной единицей и обрабатывается загрузчиком или редактором связей независимо от других секций.

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

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

Как можно видеть, язык ассемблера System/370 использует аппарат CSECT для достижения двух логически различных целей - реорганизации объектной программы (аналогично программным блокам УУМ/ДС) и разбиения программы на отдельные модули (аналогично управляющим секциям УУМ/ДС), Это часто приводит к тому, что программисты, впервые познакомившиеся с данными концепциями на подобной системе, путают эти два понятия.

Язык ассемблера System/370 предоставляет также возможность использовать управляющую секцию специального вида, которая называется ФИКТИВНОЙ СЕКЦИЕЙ (DSECT - Dummy SECTion). В DSECT может быть использовано любое предложение языка ассемблера, однако эти предложения не становятся частью объектной программы. Чаще всего DSECT используется для описания структуры сложных областей данных (записей, таблиц и т.п.), определенных вне данной программы. Метки, использованные в DSECT, определяют имена, которые могут быть использованы для адресации полей записи или таблицы (после того как будет задан подходящий базовый регистр). Дополнительную информацию о типовом ассемблере System/370 можно найти в IBM [1979], IBM [1974] и IBM [1982].

2.5.2. АССЕМБЛЕР ЭВМ VAX

В ЭВМ VAX используется намного более гибкий метод кодирования операндов, чем в большинстве машин. Имеется необычно много способов адресации, и (за небольшим исключением) каждый из этих способов может быть использован в любой команде. Спецификаторы операндов, связанные с различными способами адресации, занимают различный объем памяти. Как мы увидим, это увеличивает объем работы, выполняемой ассемблером.

Терминология, используемая в языке ассемблера VAX, несколько отличается от стандартного набора терминов, который мы использовали. Например, есть два способа адресации, в которых значения операнда хранятся как часть команды,- непосредственный и литеральный. (Для операндов этого типа мы использовали в разд.2.2 термин непосредственная адресация). Функционально эти два способа эквивалентны, и в исходной программе они задаются с помощью одной и той же нотации. Во время первого просмотра ассемблер, анализируя величину операнда, решает, какой способ адресации использовать. Если операнд является целым между 0 и 63 (или какой-либо другой величиной, которую можно разместить в 6-разрядном поле), то используется литеральный способ. Если операнд нельзя разместить в 6-разрядном поле или ассемблер еще не может определить его значение, то выбирается непосредственный способ. При непосредственном способе длина спецификатора операнда зависит от типа операнда, используемого в команде (слово, длинное слово и т.д.). Длина спецификатора операнда для литерального способа всегда равна одному байту.

Обратите внимание, что операнды VAX, использующие литеральный способ адресации, обрабатываются совершенно иначе, чем литералы, о которых мы говорили в разд.2.3. В языке ассемблера VAX нет никаких средств, которые бы соответствовали тому, что мы называли литералами в нашем ассемблере для УУМ/ДС.

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

Величина смещения для адресации относительно счетчика команд вычисляется так, как это было описано в разд.2.2. Однако в момент вычисления целевого адреса во время исполнения программы счетчик команд будет содержать не адрес следующей КОМАНДЫ, а адрес следующего СПЕЦИФИКАТОРА ОПЕРАНДА (если он есть). Это необходимо учитывать при вычислении величины смещения.

В большинстве языков ассемблера длина ассемблированной команды однозначно определяется по ее мнемоническому коду операции. Для ЭВМ VAX это не так. Каждый спецификатору операнда VAX в зависимости от типа операнда может иметь длину от 1 до 9 байт. Например, в команде

MOVB R1,R5

оба операнда находятся в регистрах. Каждый спецификатор операнда требует 1 байт; поэтому ассемблированная команда будет занимать 3 байт. В то же время в команде

MOVB 1,8(R6)

для первого операнда используется литеральный способ адресации (длина спецификатора 1 байт), а для второго - базовая: относительная адресация. С учетом величины смещения (8 ) для спецификатора второго операнда требуется 2 байт; таким образом, вся ассемблированная команда будет занимать 4 байт, В команде

MOVB Rl, ALPHA

для спецификатора первого операнда требуется 1 байт. Для второго операнда используется адресация относительно счетчика команд. В зависимости от величины смещения его спецификатор может занимать от 2 до 5 байт. Таким образом, в зависимости от адреса, назначенного метке ALPHA, данная команда может занимать от 4 до 7 байт.

Это означает, что ассемблер ЭВМ VAX должен во время первого просмотра выполнять значительно более сложную работу, чем, скажем, ассемблер System/370. Во время первого просмотра ассемблер VAX должен анализировать не только коды операций, но и операнды команд. Таблица кодов операций также должна иметь более сложную структуру, так как в ней необходимо иметь информацию о том, какие способы адресации допустимы для каждого из операндов.

Язык ассемблера VAX обеспечивает возможность организации управляющих секций, которые называются программными секциями (PSECT - Program SECTions). Подобно CSECT языка ассемблера System/370, PSECT используются для двух различных целей - реорганизации программы и разбиения программы на модули. В языке ассемблера VAX обработка PSECT отличается: меньшая часть работы выполняется ассемблером и большая - программой связывания. Если из одной PSECT происходит ссылка на имя, определенное в другой PSECT, и эти секции ассемблируются совместно, то для ссылки используется адресация относительно счетчика команд. Так как взаимное расположение секций относительно друг друга во время ассемблирования неизвестно, то величину смещения вычислить нельзя. Поэтому ассемблер генерирует команды для программы связывания, которые позволят ей определить смещение и занести его в команду. Этот процесс похож на тот, что был описан в разд.2.3.4 для разрешения ссылок между программными блоками. Более детально мы рассмотрим работу программы связывания в следующей главе.

При трансляции PSECT могут задаваться некоторые атрибуты, в том числе атрибут доступа: "только чтение", "только исполнение" и т.п. Кроме того, в каждой PSECT можно указать, разрешено или нет выравнивание элементов данных. (Проблемы выравнивания в VAX похожи на те, что мы обсуждали в связи с System/370). Программа связывания собирает вместе все сегменты каждой PSECT и все программные секции, имеющие аналогичные атрибуты. Средства операционной системы VAX могут быть использованы для обеспечения защиты PSECT в соответствии с ее атрибутами.

Ассемблер VAX предполагает по умолчанию, что все имена, которые были использованы, но не были определены во время ассемблирования, являются внешними ссылками. Никакого явного объявления внешних имен (подобно предложению ЕХТREF УУМ/ДС) не требуется. К несчастью, это приводит к тому, что ошибки типа пропуска символа в имени нельзя обнаружить до момента связывания программы. Однако программист может задать режим работы ассемблера, который отменяет данное правило умолчания и требует явного объявления внешних имен.

Дополнительную информацию об ассемблере VAX можно найти в DEG [1982] и DEC [1979].

2.5.3. АССЕМБЛЕР ЭВМ CYBER

Язык ассемблера ЭВМ CYBER, который называется COMPASS, имеет несколько существенных отличий от других языков. Первое отличие является прямым следствием структуры машины. Дело в том, что размер машинного слова CYBER позволяет разместить в нем несколько различных ассемблированных команд. В общем случае задачей ассемблера является упаковка как можно большего количества последовательно расположенных команд в одно слово объектной программы. Конечно, можно было бы просто размещать по одной машинной команде в слове, а неиспользованное пространство заполнять невыполняемыми командами, но такое решение весьма неэффективно с точки зрения как занимаемого пространства, так и времени исполнения.

Для упаковки команд в ассемблере предусмотрен СЧЕТЧИК ПОЗИЦИЙ (position counter). Этот счетчик показывает количество свободных разрядов, оставшихся в текущем слове объектной программы. В начале нового слова в счетчик позиций заносится число 60 (число разрядов слова CYBER). При генерации команды количество разрядов, требуемое для ее размещения, вычитается из счетчика позиций. Если в текущем слове нет места для размещения очередной команды, то оставшиеся разряды (если они есть) заполняются неисполняемыми командами, а для работы берется новое слово объектной программы. В этот момент счетчик размещений и счетчик слов (его мы опишем в этом разделе ниже) увеличиваются на 1, после чего они будут содержать адрес следующего слова.

Предыдущее описание представляет собой обычную последовательность действий: команды (или элементы данных) упаковываются в машинное слово до тех пор, пока это возможно, а затем осуществляется переход к следующему слову. Однако в некоторых случаях требуется перейти к следующему слову несмотря на то, что в текущем слове еще достаточно места для размещения следующего элемента объектного кода. Такая необходимость является следствием того, что в CYBER применяется пословная, а не побайтная адресация оперативной памяти. Рассмотрим, например, команду безусловного перехода. Адрес операнда в данной команде должен быть адресом слова. После того как в счетчик команд будет занесен адрес, заданный в команде перехода, следующей командой, выбираемой для выполнения, будет первая команда, упакованная в слово с адресом, равным целевому адресу команды перехода. Это означает, что мы можем непосредственно передать управление только на команду, расположенную в начале слова объектной программы.

Процесс размещения команды или элемента данных в начале слова, даже если в предыдущем слове достаточно места, называется ПРОТАЛКИВАНИЕМ (forsing upper). При этом остаток предыдущего слова заполняется неисполняемыми командами, счетчик слов и счетчик размещений увеличиваются на 1, а в счетчик позиций заносится число 60. Ассемблер COMPASS выполняет проталкивание в следующих случаях:
1. Ассемблируемое предложение имеет метку (так как адрес метки указывает на начало слова).
2. Ассемблируемое предложение является предложением резервирования памяти, и в нем не задана упаковка битовых полей в одно слово.
3. Предыдущее предложение является предложением безусловного перехода (в этом случае единственный способ выполнить следующую команду - передать на нее управление).

Программист может задать проталкивание для любого предложения, задав в поле метки символ +. Для того чтобы отменить автоматическое проталкивание, достаточно задать в поле метки символ -.

Кроме счетчика позиций ассемблер использует в своей работе еще два счетчика - СЧЕТЧИК РАЗМЕЩЕНИЙ и СЧЕТЧИК СЛОВ. Вместе эти два счетчика служат для того же, что и LOCCTR, описанный в разд.2.1. Основная функция счетчика слов та же, что и у LOCCTR: счетчик слов вместе со счетчиком позиций указывает на относительное местоположение в объектном коде следующей ассемблируемой команды. Счетчик размещений используется ассемблером для назначения адресов внутренним меткам программы. Ранее для этого мы также использовали LOCCTR.

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

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

Бек. Введение в системное программирование. 1988 8811510
Рис.2.25. Перемещение объектного кода во время исполнения программы.

Если бы объектная программа была загружена вместе с буфером и тремя разделами, то она потребовала бы большого объема памяти. Для экономии места мы можем воспользоваться приемом, показанным на рис.2.25а. Объектный код для Раздела 1 (наибольший раздел) ассемблируется и загружается обычным образом. В то же время код для Раздела 2 и Раздела 3 загружается в область буфера. Эта область не используется до тех пор, пока не начнется ввод, поэтому первоначальное размещение команд в этой области проблем не вызывает. Если для выполнения будет выбран Раздел 2 или Раздел 3, то соответствующий раздел объектного кода будет перемещен программой из области буфера на место кода Раздела 1 (рис.2.25б). Теперь требуемый раздел выбирается просто вызовом объектного кода, а буфер может использоваться для ввода данных.

Программист должен быть уверен, что при таком перемещении объектного кода метки будут определены правильно. Рассмотрим, например, команду с меткой SKIP2 на рис.2.25в (строка 11). Поскольку счетчик слов был переустановлен (строка 7), то код Разделов 2 и 3 будет расположен внутри буфера. Если бы программа ассемблировалась обычным образом, то в этом случае значением метки SKIP2 был бы адрес внутри буферной области. Однако фактически во время исполнения команды этого раздела будут расположены в памяти, общей для всех трех разделов. Поэтому целевой адрес в команде перехода в строке 10 должен быть адресом в этой области, а не в области буфера.

Бек. Введение в системное программирование. 1988 8811610
Рис. 2.25в.

Эта проблема решается с помощью задания в качестве нового значения счетчика размещений начального адреса буферной области. Напомним, что в данном ассемблере для присваивания меткам значений используется счетчик размещений, а не счетчик слов. Поэтому метке SKIP2 будет присвоено значение, соответствующее адресу, по которому она будет расположена во время исполнения, а не адресу на момент ассемблирования. Аналогично обрабатывается код Раздела 3. После того как все три раздела будут оттранслированы, первоначальное значение счетчика слов восстанавливается с помощью предложения ORG в строке 18. Предложение в строке 19 устанавливает значение счетчика размещений равным значению счетчика слов, и обычный процесс ассемблирования будет продолжен.

В COMPASS предусмотрены программные блоки (их называют ПОДПРОГРАММНЫМИ БЛОКАМИ - subprogram bloks) и управляющие секции (их называют ПОДПРОГРАММАМИ - subprograms). Их обработка в основном схожа с процессом, описанным в разд.2.3, за исключением того, что все литералы размещаются в каждой подпрограмме отдельным блоком. Для каждого подпрограммного блока ассемблер заводит отдельный набор счетчиков (слов, размещений и позиций).

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

SA1 А2
SA1 В2+TAB
SA1 Х1+В1
SA1 XI-В1

Первое из них будет ассемблировано в 15-разрядную команду с машинным кодом операции 54; второе - в 30-разрядную команду с кодом операции 51; третье - в 15-разрядную команду с кодом операции 53; четвертое недопустимо.

При интерпретации мнемонических кодов, подобных SA1 (и большинства других кодов языка ассемблера CYBER), необходимо анализировать операнды. Подобные команды исходной программы известны как СИНТАКСИЧЕСКИ ОПРЕДЕЛЯЕМЫЕ КОМАНДЫ, поскольку они распознаются по своему СИНТАКСИСУ (т.е. форме предложения). Ассемблер COMPASS обрабатывает такие команды, просматривая мнемонический код операции и операнды и извлекая символьную строку, описывающую синтаксис. Для третьего из рассмотренных выше предложений синтаксический дескриптор будет иметь вид SAX+B. Этот дескриптор определяет команду группы A, операндом которой является сумма регистров X и B. Существуют 22 различных дескриптора подполей, которые могут комбинироваться для получения таких синтаксических дескрипторов.

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

Дополнительную информацию об ассемблере CYBER можно найти в CDC [1982а].
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вс Фев 07, 2021 12:29 pm

УПРАЖНЕНИЯ

РАЗДЕЛ 2.1
1. Воспользуйтесь алгоритмом, приведенным на рис.2.4, для исходной программы на рис.2.1. Полученный вами результат должен быть таким, как показано на рис.2.2 и 2.3.

2. Воспользуйтесь алгоритмом, приведенным на рис.2.4, для следующей программы УУМ:

Бек. Введение в системное программирование. 1988 8811710

SUM START 4000
FIRST LDX ZERO
LDA ZERO
LOOP ADD TABLE,X
TIX COUNT
JLT LOOP
STA TOTAL
TABLE RESW 2000
COUNT RESW 1
ZERO WORD 0
TOTAL RESW 1
END FIRST

3. Как уже отмечалось, для некоторых операций в алгоритме на рис.2.4 не дано детального описания. (Например, операция поиска модификатора, X в поле операндов). Перечислите все, какие только можете, недетализированные операции и подумайте, как можно было бы их реализовать.

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

5. Многие ассемблеры используют свободный формат записи команд. Метки должны начинаться в первом столбце исходного предложения, но другие поля (код операции, операнды, комментарии) могут начинаться в любом столбце. Друг от друга поля отделяются пробелами. Как надо изменить алгоритм нашего ассемблера для того, чтобы он смог работать в свободном формате?

6. Алгоритм на рис.2.4 позволяет найти некоторые из ошибок в исходной программе. Однако существует много других ошибок, которые могут возникать в процессе ассемблирования программ УУМ. Когда и как каждая из этих ошибок может быть обнаружена и какие действия должен при этом предпринять ассемблер?

РАЗДЕЛ 2.2
1. Может ли ассемблер самостоятельно решить, какая из команд должна быть сгенерирована в расширенном формате? (Это позволит снять с программиста заботу о явном указании таких команд с помощью признака +).

2. Как мы уже говорили, предложение BASE служит лишь для передачи информации ассемблеру. Программист должен, кроме того, предусмотреть команды наподобие LDB для загрузки соответствующего значения в базовый регистр. Может ли ассемблер автоматически генерировать команду LDB по директиве BASE? Если да, то в чем заключаются плюсы и минусы подобной интерпретации предложения BASE?

3. Сгенерируйте объектный код для каждого предложения следующей программы УУМ/ДС:

Бек. Введение в системное программирование. 1988 8811810

SUM START 0
FIRST LDX #0
LDA #0
+LDB #TABLE2
BASE TABLE2
LOOP ADD TABLE,X
ADD TABLE2,X
TIX COUNT
JLT LOOP
+STA TOTAL
RSUB
COUNT RESW 1
TABLE RESW 2000
TABLE2 RESW 2000
TOTAL RESW 1
END FIRST

4. Сгенерируйте законченную объектную программу для исходной программы из упр.3.

5. Модифицируйте алгоритм, показанный на рис.2.4, так, чтобы он обрабатывал все допустимые в УУМ/ДС способы адресации. Как отразятся эти изменения на предложенной вами в упр.2.1.4 модульной структуре?

6. Модифицируйте алгоритм, показанный на рис.2.4, так, чтобы он позволял получать перемещаемые программы. Как отразятся эти изменения на предложенной вами в упр.2.1.4 модульной структуре?

7. Формат записи-модификатора хорошо подходит для программ УУМ/ДС потому, что все адресные поля в командах и данных располагаются, начиная с границы полубайта. Как должна была бы выглядеть запись-модификатор, если бы это было бы не так (т.е. если бы адресные поля могли бы начинаться с произвольного места внутри байта и могли бы иметь произвольную длину)?

8. Предположим, что мы сделали программу на рис.2.1 перемещаемой. Эта программа написана для стандартной модели УУМ, поэтому все адреса операндов являются фактическими адресами и имеется, только один командный формат. Во время ее загрузки практически каждая команда объектной программы нуждается в модификации адресов операндов. Это потребует большого количества записей-модификаторов (что более чем в два раза увеличит размер объектной программы). Как можно было бы включить информацию, необходимую для перемещения программы, без столь значительного увеличения размера объектной программы?

РАЗДЕЛ 2.3
1. Модифицируйте алгоритм, показанный на рис.2.4, так, чтобы он мог обрабатывать литералы.

2. Можно ли было в программе, приведенной на рис.2.9, в строках 135 и 145 использовать литералы? Почему мы в этом случае не стали их использовать?

3. Немного расширив нашу литеральную нотацию, мы могли бы записать команду в строке 55 рис.2.9 в виде

LDA =W'3'

указав, что литеральный операнд является словом и имеет значение, равное 3. Хорошо ли это?

4. Непосредственные операнды и литералы - это два способа задания значения операнда непосредственно в исходном предложении. Каковы преимущества и недостатки каждого из них? В каких случаях один предпочтительнее другого?

5. Предположим, что мы внесли следующие изменения в программу на рис.2.9:
а) Исключили предложение LTORG в строке 93.
б) Заменили предложение в строке 45 на +LDA ... .
в) Заменили операнды в строках 135 и 145 на литералы (и исключили строку 185).

Каким будет з этом случае объектный код для строк 45, 135, 145, 215 и 230? Как будет в этом случае выглядеть литеральный пул? Замечание: для выполнения этой работы полная перетрансляция программы не требуется.

6. Предположим, что имена ALPHA и BETA являются метками исходной программы. Объясните, чем различаются две следующие последовательности предложений:
а) LDA ALPHA-BETA
б) LDA ALPHA SUB BETA

7. Чем различаются нижеприведенные последовательности предложений:
а) LDA #3
б) THREE EQU 3
...
LDA #THREE
в) THREE EQU 3
...
LDA THREE

8. Модифицируйте алгоритм, показанный на рис.2.4, так, чтобы он мог работать с несколькими программными блоками.

9. Модифицируйте алгоритм, показанный на рис.2,4, так, чтобы он мог работать с несколькими управляющими секциями.

10. Предположим, что в ассемблере реализованы все возможности, описанные в разд.2.3. Как следует в этом случае изменить таблицу имен по сравнению с тем, как она описана в разд.2.1?

11. Если различные управляющие секции ассемблируются совместно, то некоторые ссылки между ними могут быть обработаны ассемблером (вместо того чтобы оставлять их загрузчику). Например, в программе, приведенной на рис.2.15, выражение в строке 190 может быть вычислено непосредственно ассемблером, так как его таблица имен содержит всю необходимую информацию. В чем заключаются преимущества и недостатки ракой обработки?

12. Пусть в программе, приведенной на рис.2.11, мы использовали только два программных блока - непоименованный блок и CBLKS. Предположим, что элементы данных CDATA включены в непоименованный блок. Какие надо для этого сделать изменения в исходной программе? Покажите, какая в этом случае будет получена объектная программа.

13. Предположим, что метка LENGTH определена так, как в программе, показанной на рис.2.9. Чем различаются две нижеследующие последовательности предложений?
а) LDA LENGTH
SUB #1
б) LDA LENGTH-1

14. В соответствии с определениями имен на рис.2.10 укажите значения, тип и интуитивный смысл (если он есть) для каждого из следующих выражений:
а) BUFFER-FIRST
б) BUFFER+4095
в) MAXLEN-1
г) BUFFER+MAXLEN-1
д) BUFFER-MAXLEN
е) 2*LENGTH
ж) 2*MAXLEN - 1
з) MAXLEN-BUFFER
и) FIRST+BUFFER
к) FIRST-BUFFER+BUFEND

15. В программе, приведенной на рис.2.9, строка 107 записана как

MAXLEN EQU BUFEND-BUFFER

Чем это лучше записи вида

MAXLEN EQU 4096

16. Можно ли в программе, приведенной на рис.2.15, заменить строку 190 на

MAXLEN EQU BUFEND-BUFFER

а строку 133 на

+LDT #MAXLEN

как мы это сделали на рис.2.9?

17. Ассемблер мог бы просто предполагать, что все ссылки на имена, не определенные в данной управляющей секции, являются внешними ссылками. В этом случае отпадает необходимость в предложении EXTREF. Хорошо ли это?

18. Каким образом ассемблер, допускающий внешние ссылки, мог бы обойтись без предложения EXTDEF? Какие здесь есть преимущества и недостатки?

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

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

21. Предположим, что имена RDREC и COPY определены так, как показано на рис.2.15. В соответствии с нашими правилами выражение

RDREC-COPY

является недопустимым (т.е. ассемблер и/или загрузчик откажутся его обрабатывать). Предположим, что в силу каких-либо причин программе действительно необходимо значение этого выражения. Как это можно сделать, не меняя правил составления выражений?

22. Мы рассмотрели большое количество директив ассемблера, и еще многие другие директивы могли бы быть реализованы в реальных ассемблерах. Их поиск путем последовательного сравнения может оказаться весьма неэффективным. Как мы смогли бы использовать таблицу, например похожую на OPTAB, для того, чтобы ускорить распознавание и обработку директив ассемблера? (Указание: ответ может зависеть от языка, на котором написан ассемблер).

23. Что кроме листинга исходной программы со сгенерированным объектным кодом могло бы быть полезно для программиста? Предложите несколько вариантов листингов, которые могут быть сгенерированы, и рассмотрите структуры данных или алгоритмы, необходимые для их построения.

РАЗДЕЛ 2.4
1. Рассмотрите базовый ассемблер, алгоритм которого показан на рис.2.4. Какие таблицы и подпрограммы следует включить в корневой сегмент этого ассемблера?

2. Какие таблицы и подпрограммы следует включить в корневой сегмент усовершенствованного ассемблера, в котором реализованы возможности, описанные в разд.2.3?

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

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

Бек. Введение в системное программирование. 1988 8812210

Имя. Строка определения. Строка использования
COPY. 5.
FIRST. 10. 255
CLOOP. 15. 40
ENDFIL. 45. 30
EOF. 80. 45
RETADR. 95. 10, 70
LENGTH. 100. 12, 13, 20, 60, 175, 212
...

Как ассемблер может это сделать? Укажите изменения, которые потребуется при этом внести в алгоритм и таблицы, описанные в разд.2.1.

5. Может ли однопросмотровый ассемблер создавать перемещаемую объектную программу и обрабатывать внешние ссылки? Опишите требуемые для этого алгоритмы обработки и укажите потенциальные трудности.

6. Как можно в однопросмотровом ассемблере реализовать литералы?

7. Мы рассмотрели однопросмотровые ассемблеры, в которых операндами команд могут быть только одиночные имена. Как сделать так, чтобы однопросмотровый ассемблер мог обрабатывать команды вида

JEQ ENDFIL+3

где имя ENDFIL еще не определено?

8. Опишите алгоритм простого однопросмотровсго ассемблера, работающего по схеме загрузка-выполнение.

9. Предположим, что команда, в которой используется ссылка вперед, должна ассемблироваться с помощью адресации относительно счетчика команд. Как можно ее обрабатывать с помощью однопросмотрового ассемблера?

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

11. Как можно расширить метод, описанный в разд.2.4.3, для обработки ссылок вперед в предложениях ORG?
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пн Фев 08, 2021 12:39 pm

ГЛАВА 3. ЗАГРУЗЧИКИ И ПРОГРАММЫ СВЯЗЫВАНИЯ

Как мы видели, объектная программа содержит оттранслированные команды и значения данных, полученные из исходной программы, и, кроме того, определяет адреса в оперативной памяти, куда эти команды и данные должны помещаться, В гл.2 мы познакомились с тремя следующими процессами:
1. ЗАГРУЗКОЙ, обеспечивающей размещение программы в оперативной памяти для исполнения.
2. ПЕРЕМЕЩЕНИЕМ, которое позволяет модифицировать объектную программу так, что она может загружаться с адреса, отличного от первоначально заданного (см. разд.2.2.2).
3. СВЯЗЫВАНИЕМ, обеспечивающим объединение двух или более раздельно оттранслированных объектных программ и предоставляющим информацию, необходимую для разрешения ссылок между ними (см. разд.2.3.5).

ЗАГРУЗЧИК - это системная программа, выполняющая загрузку. Многие загрузчики обеспечивают, кроме того, перемещение и связывание. В некоторых системах функция связывания отделена от функций перемещения и загрузки. Связывание выполняется специальной ПРОГРАММОЙ СВЯЗЫВАНИЯ (или РЕДАКТОРОМ СВЯЗЕЙ), перемещение и загрузка - ЗАГРУЗЧИКОМ. В большинстве случаев трансляторы (т.е. ассемблеры и компиляторы) создают в каждой конкретной системе объектный код в некотором стандартном формате. Таким образом, загрузчик и программа связывания могут использоваться вне зависимости от того, на каком языке была написана исходная программа.

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

Как и в предыдущей главе, мы вначале рассмотрим основную функцию изучаемого программного обеспечения - в данном случае загрузку объектной программы в оперативную память для последующего ее исполнения. В разд.3.1 представлен АБСОЛЮТНЫЙ ЗАГРУЗЧИК (absolute loader). Подобный загрузчик мог бы использоваться для УУМ совместно с ассемблером, описанным в разд.2.1.

В разд.3.2 разбираются вопросы перемещения и связывания программ с точки зрения загрузчика. Мы обсудим несколько возможных способов представления объектной программы и исследуем, как они связаны со структурой ЭВМ. Мы также рассмотрим СВЯЗЫВАЮЩИЙ ЗАГРУЗЧИК (linking loader), представляющий собой наиболее совершенный тип загрузчика, который используется в большинстве современных вычислительных систем, В разд.3.3 приведены некоторые из наиболее часто встречающихся свойств загрузчика, которые не имеют прямой связи со структурой машины. Как и прежде, нашей целью является не рассмотрение всех возможных свойств, а изучение концепций и технических приемов, наиболее часто применяемых при создании загрузчиков.

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

Наконец, в разд.3.5 мы вкратце обсудим некоторые примеры реальных загрузчиков и программ связывания. Мы рассмотрим те же машины, для которых в разд.2.5 были описаны ассемблеры, и укажем на взаимосвязь, существующую между ассемблером и загрузчиком. Как и ранее, мы сосредоточим наше внимание на тех аспектах системных компонент, которые зависят от особенностей аппаратуры или программного обеспечения.

3.1. ОСНОВНЫЕ ФУНКЦИИ ЗАГРУЗЧИКА

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

Бек. Введение в системное программирование. 1988 8812510
Рис.3.1. Загрузка абсолютной программы.

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

На рис.3.2 показан алгоритм рассмотренного нами простого загрузчика. Хотя данный процесс крайне прост, все же есть один аспект, заслуживающий комментария. В нашей объектной программе каждый байт ассемблированного кода дается в шестнадцатеричном символьном представлении. Например, машинный код операции для команды STL представляется в виде ПАРЫ СИМВОЛОВ 14. Когда они считываются загрузчиком (как часть объектной программы), они будут занимать ДВА байта памяти. Однако в команде, которая загружается для выполнения, этот код операции должен быть записан в ОДНОМ байте с ШЕСТНАДЦАТЕРИЧНЫМ ЗНАЧЕНИЕМ 14. Таким образом, каждая пара байтов объектной программы должна быть упакована во время загрузки в один байт. Очень важно четко понимать, что на рис. 3.1а каждый символ представляет один байт записи объектной программы. С другой стороны, на рис.3.1б каждый символ представляет одну ШЕСТНАДЦАТЕРИЧНУЮ ЦИФРУ (т.е. полбайта).

Бек. Введение в системное программирование. 1988 8812610
Рис.3.2. Алгоритм абсолютного загрузчика.

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

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


Последний раз редактировалось: Gudleifr (Пн Фев 08, 2021 4:26 pm), всего редактировалось 1 раз(а)
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Пн Фев 08, 2021 12:40 pm

3.2. МАШИННО-ЗАВИСИМЫЕ СВОЙСТВА ЗАГРУЗЧИКОВ

Абсолютный загрузчик, описанный в разд.2.1, конечно, весьма прост и эффективен, однако данная схема имеет ряд потенциальных недостатков. Одним из наиболее очевидных является то, что от программиста требуется определять фактический адрес начала загрузки программы во время ее ассемблирования. Если мы рассматриваем очень простую ЭВМ с небольшим объемом оперативной памяти (такую, как стандартная модель УУМ), то это не создает особых проблем. В этом случае места достаточно только для выполнения одной программы, и поэтому начальный адрес загрузки известен заранее. Для более совершенных машин, обеспечивающих работу с большим объемом оперативной памяти (подобных УУМ/ДС), ситуация не столь проста. Очень часто желательно одновременно выполнять несколько независимых программ, совместно использующих оперативную память и другие ресурсы. Это означает, что мы не знаем заранее, куда будет загружена программа. Поэтому для того, чтобы обеспечить эффективное разделение ресурсов машины, требуется создавать перемещаемые, а не абсолютные программы.

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

В этом разделе мы рассмотрим более сложный загрузчик. Такой загрузчик вполне подходит для УУМ/ДС и является типичным для большинства современных ЭВМ. Наряду с простой функцией размещения программы в оперативной памяти, описанной в предыдущем разделе, данный загрузчик выполняет, также перемещение и связывание программ. Одним из вопросов, которого мы коснемся в данном разделе, будет вопрос о влиянии структуры машины на решения, применяемые при проектировании загрузчика.

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

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

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

3.2.1. ПЕРЕМЕЩЕНИЕ

Загрузчики, обеспечивающие перемещение программ, называются ПЕРЕМЕЩАЮЩИМИ ЗАГРУЗЧИКАМИ (relocating loaders) или ОТНОСИТЕЛЬНЫМИ ЗАГРУЗЧИКАМИ (relative loaders). С концепцией перемещения программ мы познакомились в разд.2.2.2. Вам, прежде чем продолжить чтение, следует бегло повторить материал этого раздела. В данном разделе мы обсудим два способа, в которых информация о перемещении задается как составная часть объектной программы.

Первый из рассматриваемых способов в основном аналогичен тому, что мы обсуждали в гл.2. Для описания каждого фрагмента объектного кода, требующего изменения при перемещении, используется специальная запись-модификатор. (Ее формат описан в разд.2.3.5.) На рис.3.3 показана программа для УУМ/ДС, которую мы будем использовать для иллюстрации первого способа задания информации о перемещении. Это та же самая программа, что и на рис.2.6. Здесь мы ее воспроизводим исключительно для удобства. Большинство команд данной программы использует относительную или непосредственную адресацию. Единственной частью объектной программы, содержащей фактические адреса, являются команды, в которых используется расширенный командный формат (строки 15, 35 и 65). Поэтому при перемещении программы изменение значений потребуется только для этих величин.

Бек. Введение в системное программирование. 1988 8812910
Рис.3.3. Пример программы для УУМ/ДС (с рис.2.6).

На рис.3.4 показана объектная программа, соответствующая исходному тексту на рис.3.3. Обратите внимание, что для каждого значения, требующего изменения при перемещении, имеется отдельная запись-модификатор (в данном случае это три ранее упомянутые команды). Каждая запись-модификатор определяет начальный адрес и длину поля, значение которого необходимо изменить, а также тип требуемой модификации. В данном примере все модификации заключаются в прибавлении значения метки COPY, представляющей собой начальный адрес программы. Алгоритм, используемый загрузчиком для выполнения данной операции, описывается в разд.3.2.3. Дополнительные примеры определяемого подобным образом перемещения будут приведены в следующем разделе при изучении взаимосвязи между перемещением и связыванием.

Бек. Введение в системное программирование. 1988 8813010
Рис.3.4. Объектная программа, в которой перемещение задается с помощью записей-модификаторов.

Записи-модификаторы являются удобным средством для задания информации о перемещении программы. Однако данная схема годится не для всех машин. Рассмотрим, например, программу, приведенную на рис.3.5. Это перемещаемая программа, написанная для стандартной модели УУМ. Ее важным отличием от программы, изображенной на рис.3.3, является то, что стандартная модель УУМ не имеет относительной адресации. В данной программе адреса всех команд, за исключением RSUB, должны модифицироваться при перемещении. Поэтому для задания информации о перемещении необходимо иметь 31 запись-модификатор, что потребует более чем двукратного увеличения объема памяти, занимаемой объектной программой, по сравнению с программой, показанной на рис.3.4.

Бек. Введение в системное программирование. 1988 8813110
Рис.3.5. Перемещаемая программа для стандартной модели УУМ.

На машинах, где преимущественно используются прямая адресация и фиксированный командный формат очень часто более эффективным оказывается другой способ задания информации о перемещении. На рис.3.6 показано, как этот способ может быть применен к нашему примеру. В данном случае записи-модификаторы не используются. Формат записей тела программы тот же, что и ранее, за исключением того, что теперь с каждым словом объектного кода связан РАЗРЯД ПЕРЕМЕЩЕНИЯ. Поскольку все команды УУМ занимают одно слово, это означает, что для каждой потенциальной команды имеется один разряд перемещения. Эти разряды собираются вместе и образуют МАСКУ, которая записывается после указателя длины каждой записи тела программы. На рис.3.6 эта маска (в символьном виде) показана в виде трех шестнадцатеричных цифр (на рисунке они подчеркнуты).

Бек. Введение в системное программирование. 1988 8813210
Рис.3.6. Объектная программа, в которой перемещение задается с помощью маски перемещения.

Если разряд перемещения установлен в 1, то при перемещении программы начальный адрес программы добавляется к слову, соответствующему этому разряду. Значение разряда перемещения, равное 0, показывает, что никаких преобразований при перемещении делать не надо. Если запись тела программы содержит менее 12 слов объектного кода, то разряды, соответствующие неиспользуемым словам, устанавливаются в 0. Таким образом, в первой записи тела программы маска FFC (представляющая собой строку 111111111100) показывает, что все 10 слов объектного кода должны быть модифицированы при перемещении программы. В данных словах находятся команды, соответствующие в исходной программе строкам с 10 по 55 (см. рис.3.5). Маска E00 во второй записи тела программы определяет, что модификация требуется для трех первых слов. Оставшаяся часть объектного кода в данной записи представляет собой константы (и команду RSUB), не требующие модификации.

Аналогично строятся остальные записи тела программы. Обратите внимание, что объектный код, генерируемый для команды LDX в строке 210, начинает новую запись тела программы, хотя в предыдущей записи достаточно места для его размещения. Это произошло потому, что каждый разряд перемещения связан с 3-байтовым сегментом объектного кода (словом) в записи тела программы. Поэтому каждое значение, требующее модификации при перемещении, должно совпадать с одним из этих 3-байтовых сегментов для того, чтобы ему можно было поставить в соответствие разряд перемещения. Ассемблированная команда LDX действительно требует модификации, поскольку в ней используется прямая адресация. Однако, если бы она была помещена в предыдущую запись тела программы, она не была бы правильно выравнена для соответствия разряду перемещения, так как в строке 185 была сгенерирована 1-байтовая константа. Поэтому данная команда должна начинать в объектной программе новую запись тела программы.

Вам следует тщательно изучить оставшуюся часть объектной программы на рис.3.6 и разобраться с тем, как ассемблер генерирует разряды перемещения и как они используются загрузчиком.

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

3.2.2. СВЯЗЫВАНИЕ ПРОГРАММ

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

На рис.2.15 в разд.2.3.5 была показана программа, состоящая из трех управляющих секций. Эти управляющие секции могут ассемблироваться как вместе (т.е. за одно обращение к ассемблеру), так и независимо друг от друга. В любом случае после ассемблирования они будут представлены в виде отдельных сегментов объектного кода (рис.2.17). Однако программист, естественно, склонен рассматривать программу в виде логического целого, включающего в себя все связанные управляющие секции. Однако с точки зрения загрузчика никакой программы нет вообще. Имеются только управляющие секции, которые должны связываться, перемещаться и загружаться. Загрузчик никоим образом не может узнать (да это ему и не нужно) о том, какие управляющие секции ассемблировались одновременно.

Рассмотрим три (раздельно ассемблированные) программы, изображенные на рис.3.7. Каждая из них состоит из единственной управляющей секции и содержит списки (LISTA, LISTB, LISTC). Концы этих списков помечены метками ENDA, ENDB, ENDC. Метки в начале и в конце списков являются внешними именами (т.е. они доступны для связывания). Обратите внимание, что каждая программа содержит один и тот же набор ссылок к этим внешним именам. Три из них - операнды команд (предложения с метками REF1-REF3), а остальные - ссылки на элементы данных длиной в одно слово (REF4-REF8 ). На данном примере мы посмотрим, в чем состоят различия в обработке этих идентичных выражений в указанных трех программах, и подчеркнем взаимосвязь между перемещением и связыванием. Для того чтобы сосредоточиться на данном вопросе, мы сознательно не стали приводить реальные программы. Те части программ, которые не используются в процессах перемещения и связывания, опущены. То же самое относится и к сгенерированному для этих программ объектному коду (см.рис.3.8 ).

Бек. Введение в системное программирование. 1988 8813510
Рис.3.7. Модельная программа, иллюстрирующая связывание и перемещение.

Бек. Введение в системное программирование. 1988 8813610
Рис.3.7. Продолжение.

Бек. Введение в системное программирование. 1988 8813710
Рис. 3.8. Объектная программа соответствующая рис.3.7.

Рассмотрим вначале предложение с меткой REF1. Для первой программы (PROGA) REF1 является просто ссылкой на метку внутри данной программы. Она ассемблируется обычным образом как команда, использующая адресацию относительно счетчика команд. Никаких модификаций для перемещения или связывания не требуется. В PROGB, с другой стороны, тот же самый операнд ссылается на внешнее имя. Ассемблер использует для этой команды расширенный командный формат и заносит в адресное поле значение 000000. Соответствующая ей объектная программа (см. рис.3.8 ) содержит запись-модификатор, в котором загрузчику предписывается добавить значение имени LISTA к данному адресному полю в момент связывания программы. Совершенно аналогично эта ссылка обрабатывается для PROGC.

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

В команде с меткой REF3 используется непосредственный операнд, значением которого является разность между ENDA и LISTA (т.е. длина списка в байтах). В PROGA ассемблер располагает всей необходимой информацией для вычисления этого значения. Однако при ассемблировании PROGB (и PROGC) значения меток неизвестны. В этих программах выражение должно вычисляться как внешняя ссылка (с двумя записями-модификаторами) даже несмотря на то, что в конечном счете результат является абсолютной величиной, не зависящей от места загрузки программы.

Оставшиеся ссылки иллюстрируют другие возможные варианты. Общий подход, принятый при ассемблировании, заключается в том, что ассемблер стремится вычислить как можно большую часть выражения. Оставшиеся термы передаются загрузчику с помощью записей-модификаторов. Для того чтобы разобраться в этом, рассмотрим предложение с меткой REF4. В PROGA ассемблер может вычислить все выражение, за исключением значения LISTC. В результате имеем начальное значение 000014 (шестнадцатеричное) и одну запись-модификатор. Однако в PROGB то же самое выражение не содержит ни одного терма, который может быть вычислен ассемблером. Поэтому объектный код будет содержать начальное значение, равное 000000, и три записи-модификатора. В PROGC ассемблер знает относительное значение LISTC (но не фактический адрес, который неизвестен до тех пор, пока программа не будет загружена в память). Начальное значение данной константы будет содержать относительный адрес LISTC (шестнадцатеричная величина 000030). Записи-модификаторыL содержат указания загрузчику прибавить начальный адрес программы (т.е. значение PROGC), прибавить значение ENDA и вычесть значение LISTA. Таким образом, выражение KEF4 представляет собой простую внешнюю ссылку в случае PROGA, более сложную внешнюю ссылку в случае PROGB и комбинацию перемещения с внешней ссылкой в случае PROGC.

Вам следует самостоятельно разобраться с оставшимися ссылками (с REF5 по REF8 ) и убедиться, что вы правильно поняли, как были сгенерированы объектный код и записи-модификаторы, показанные на рис.3.8.

На рис.3.9а приведены три рассмотренные нами программы после загрузки и связывания. Программа PROGA была загружена, начиная с адреса 4000. Непосредственно после нее были загружены PROGB и PROGC. Заметим, что в результате (после перемещения и связывания) каждое выражение с REF4 по REF8 будет иметь одно и то же значение во всех трех программах. Так и должно было быть, поскольку в исходных программах они имели один и тот же вид.

Бек. Введение в системное программирование. 1988 8813810
Рис.3.9а. Программа рис.3.8 после связывания и загрузки. 3.9б. Выполнение операций связывания и перемещения для предложения REF4 из PROGA.

Рассмотрим, например, значение метки REF4 в PROGA. Она расположена по адресу 4054 (этот адрес вычисляется как сумма начального адреса PROGA и относительного адреса REF4, равного 0054). На рис.3.9б детально показан процесс вычисления этого значения. Начальное значение (полученное из записи тела программы) равно 000014. К нему прибавляется адрес, назначенный метке LISTC и равный 4112 (начальный адрес PROGC плюс 30). В PROGB значение REF4 расположено по относительному адресу 70 (фактический адрес 40D3). К начальному значению (000000) загрузчик прибавляет значения меток ENDA (4054) и LISTC (4112), а затем вычитает значение метки LISTA (4040). В результате будет получено значение 004126, т.е. точно такое же, как и в PROGA. Значение REF4 в PROGC вычисляется аналогично и дает тот же самый результат. Те же самые рассуждения справедливы и для всех других меток с REF5 по REF8.

Если одно и то же выражение используется в качестве операнда в различных командах, то после загрузки адресные поля этих команд необязательно должны иметь одинаковые значения. Это происходит потому, что при вычислении адреса операнда для команд, использующих адресацию относительно счетчика команд (или базы), выполняется еще один дополнительный шаг. В данном случае важно, чтобы совпадали целевые адреса команд. Например, в PROGA команда с меткой REF1 использует адресацию относительно счетчика команд. Значение смещения для данной команды равно 01D. В момент исполнения этой команды в счетчике команд будет находиться величина 4023 (фактический адрес следующей команды). В результате будет получен целевой адрес 4040. Данная команда не требует какой-либо модификации при перемещении программы, так как счетчик команд всегда будет содержать фактический (а не относительный) адрес следующей команды. Мы можем рассматривать данный процесс как автоматическое выполнение требуемого перемещения в момент исполнения программы путем вычисления целевого адреса. С другой стороны, в программе PROGB команда с меткой REF1 использует расширенный командный формат, в котором фактический адрес задается непосредственно. После связывания значение данного адреса будет равно 4040, т.е. будет совпадать со значением целевого адреса соответствующей команды PROGA.

Вам следует тщательно разобраться с остальными предложениями и убедиться, что целевые адреса (в случае REF2 и REF3) и значения констант (для REF5-REF8 ) одни и те же в каждой из трех программ. Сейчас вас не должно беспокоить, как загрузчик реально выполняет данные вычисления, поскольку необходимые для этого алгоритмы и структуры данных будут обсуждаться в следующем разделе. Важно, однако, чтобы вы поняли, какие именно вычисления надо делать, и могли выполнить их вручную (следуя при этом инструкциям, содержащимся в объектных программах).

3.2.3. ТАБЛИЦЫ И АЛГОРИТМЫ СВЯЗЫВАЮЩЕГО ЗАГРУЗЧИКА

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

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

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

Сам алгоритм показан на рис.3.10а и 3.10б. В процессе обсуждения этого алгоритма вам, возможно, будет полезно вспомнить примеры загрузки и связывания, рассмотренные в предыдущем разделе (рис.3.8 и 3.9).

Бек. Введение в системное программирование. 1988 8814110
Рис.3.10а. Алгоритм первого просмотра связывающего загрузчика.

Бек. Введение в системное программирование. 1988 8814210
Рис.3.10б. Алгоритм второго просмотра связывающего загрузчика.

Во время первого просмотра (рис.3.10а) загрузчик обрабатывает только запись-заголовок и записи-определения управляющих секций. Начальный адрес загрузки связываемой программы (PROGADDR) загрузчик получает от операционной системы. Этот адрес становится начальным адресом (CSADDR) первой управляющей секции входного потока. Имя управляющей секции, полученное из записи-заголовка, записывается в ESTAB и ему присваивается текущее значение CSADDR. Все внешние имена из записей-определений также заносятся в ESTAB. Значения их адресов получаются путем сложения значения из записи-определения с CSADDR. После того, как будет прочитана запись-конец, к CSADDR добавляется длина управляющей секции (CSLTH). (Длина была получена из записи-заголовка). Таким образом, мы получаем начальный адрес для следующей управляющей секции.

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

Бек. Введение в системное программирование. 1988 8814310

Управляющая секция. Имя. Адрес. Длина
PROGA. . 4000. 0063
. LISTA. 4040.
. ENDA. 4054.
PROGB. . 4063. 007F
. LISTB. 40C3.
. ENDB. 40D3.
PROGC. . 40E2. 0051
. LISTC. 4112.
. ENDC. 4124.

По существу это та же информация, которая содержится в ESTAB в конце первого просмотра.

Собственно загрузка, перемещение и связывание программы осуществляются во время второго просмотра (рис.3.106). Переменная CSADDR используется так же, как и во время первого просмотра. Она всегда содержит фактический начальный адрес загружаемой в данный момент управляющей секции. После того, как считана очередная запись тела программы, содержащийся в ней объектный код помещается по указанному адресу (плюс текущее значение CSADDR). Когда встречается запись-модификатор, то имя, требуемое для модификации, ищется в ESTAB и затем его значение прибавляется к заданному адресу или вычитается из него.

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

Вам следует применить данный алгоритм (вручную) для загрузки и связывания объектной программы, изображенной на рис.3.8. Если для PROGADDR использовать 4000, то полученный результат должен быть таким, как показано на рис.3.9.

Можно несколько повысить эффективность данного алгоритма, если слегка изменить формат объектной программы. Суть этих изменений заключается в том, что каждой внешней ссылке присваивается специальный ССЫЛОЧНЫЙ НОМЕР. Эти ссылочные номера используются в записях-модификаторах вместо имен.

Предположим, что имя управляющей секции всегда имеет ссылочный номер 01. Ссылочные номера остальных внешних ссылок определены в записи-ссылке объектной программ данной управляющей секции. На рис.3.11 показано, как будет выглядеть объектная программа, изображенная на рис.3.8, после внесения в нее указанных изменений. Для удобства чтения объектной программы ссылочные номера подчеркнуты. Описанный здесь механизм часто используется в реальных загрузчиках, и это бы-ла одна из причин, почему мы включили в наше объектное представление записи-ссылки.

Бек. Введение в системное программирование. 1988 8814410
Рис.3.11. Объектная программа, соответствующая рис.3.7, в которой используются ссылочные номера для модификации кодов. (Для удобства чтения ссылочные номера подчеркнуты).

Вы могли заметить, что в алгоритме на рис.3.10 эти записи не использовались.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вт Фев 09, 2021 10:28 am

3.3. МАШИННО-НЕЗАВИСИМЫЕ СВОЙСТВА ЗАГРУЗЧИКОВ

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

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

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

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

3.3.1. АВТОМАТИЧЕСКИЙ ПОИСК В БИБЛИОТЕКАХ

Многие связывающие загрузчики допускают автоматическое включение библиотечных подпрограмм в загружаемую программу. В большинстве случаев для этой цели используется некоторая стандартная библиотека. Другие библиотеки могут подключаться с помощью специальных управляющих директив или передаваться загрузчику в качестве параметров. Этот механизм позволяет программисту использовать подпрограммы из одной или нескольких библиотек (например, математические или статистические подпрограммы) почти так же, как если бы они были составной частью языка программирования. Подпрограмма, вызываемая из загружаемой программы пользователя, автоматически выбирается из библиотеки, связывается с главной программой и загружается в оперативную память. От программиста не требуется ничего, кроме как перечислить имена используемых подпрограмм в исходной программе в списке внешних имен. В некоторых системах данный механизм называется АВТОМАТИЧЕСКИМ БИБЛИОТЕЧНЫМ ВЫЗОВОМ (automatic library call). Мы используем термин БИБЛИОТЕЧНЫЙ ПОИСК (library search) для того, чтобы не путать его со средствами вызова, которые имеются в большинстве языков программирования.

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

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

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

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

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

3.3.2. УПРАВЛЕНИЕ ПРОЦЕССОМ ЗАГРУЗКИ

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

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

Одной из типичных возможностей, предоставляемых пользователю, является определение альтернативного источника входного потока. Например, команда

INCLUDE имя-программы (имя-библиотеки)

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

Другие команды дают пользователю возможность исключить некоторые внешние имена или целые управляющие секции. Кроме того, может разрешаться изменять внешние ссылки во время загрузки и связывания программ. Например, команда

DELETE имя-секции

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

CHANGE имя1,имя2

может использоваться для замены внешнего имени имя1 на имя2. Ниже приводится пример использования этих команд.

Рассмотрим исходную программу, изображенную на рис.2.15, и соответствующую ей объектную программу на рис.2.17. Имеется главная программа (COPY), которая использует две подпрограммы (RDREC и WRREC). Каждая из программ оформлена в виде отдельной управляющей секции. Если RDREC и WRREC разработаны только для использования в программе COPY, то весьма вероятно, что все три управляющие секции будут ассемблироваться одновременно. Это означает, что все три управляющие секции будут расположены в одном файле {или будут включены в одну и ту же библиотеку).

Предположим теперь, что в вычислительной системе появился набор сервисных подпрограмм. Две из них, READ и WRITE, предназначены для выполнения тех же самых функций, что и RDREC и WRREC. Вероятно, желательно будет изменить исходную программу COPY так, чтобы она могла использовать эти сервисные подпрограммы. В качестве временной меры можно использовать последовательность команд загрузчика, которые позволяют сделать эти изменения без переассемблирования программы. Например, это может быть полезно для того, чтобы протестировать сервисные подпрограммы прежде, чем делать окончательное преобразование COPY.

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

INCLUDE READ(UTLIB)
INCLUDE WRITE(UTLIB)
DELETE RDREC,WRREC
CHANGE RDREC,READ
CHANGE WRREC,WRITE

Эти команды предписывают загрузчику включить в рассмотрение управляющие секции READ и WRITE из библиотеки UTLIB и исключить из рассмотрения управляющие секции RDREC и WRREC. Первая команда CHANGE служит для замены всех ссылок к RDREC на READ. Аналогично ссылки к WRREC заменяются на WRITE. Результат будет в точности таким, как если бы исходная программа COPY была модифицирована для использования READ и WRITE. Вам предлагается самостоятельно подумать над тем, как загрузчик может осуществить обработку команд, обеспечивающих описанные действия.

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

LIBRARY MYLIB

Такие пользовательские библиотеки обычно просматриваются раньше стандартных системных библиотек, что позволяет использовать в программе специальные версии стандартных подпрограмм.

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

NOCALL STDDEV,PLOT,CORREL

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

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

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

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

3.3.3. ОВЕРЛЕЙНАЯ СТРУКТУРА ПРОГРАММ

В разд.2.4.1 мы обсуждали реализацию двухпросмотровога ассемблера с простой оверлейной структурой. Такой способ реализации позволяет сократить суммарный объем оперативной памяти, требуемой для ассемблирования, так как для первого и второго просмотров используется одно и то же пространство. В этом разделе мы рассмотрим более сложный пример и обсудим, как осуществляется управление самим оверлейным процессом. В качестве введения в данную тему вам следует обратиться к рис.2.18 и 2.19 и материалу разд.2.4.1.

На рис. 3.12а показана программа с оверлейной структурой, которую мы будем использовать в качестве примера. Буквами обозначены имена управляющих секций, а линиями - связи между ними по передаче управления. Таким образом, КОРНЕВАЯ управляющая секция (соответствующая ведущей подпрограмме на рис.2.18 ) имеет имя A. Управляющая секция A может вызывать секции B, C или D/E; секция B может вызывать F/G или H и т.д. Обозначение D/E используется для спецификации двух управляющих секций (D и Е), которые тесно связаны между собой и всегда используются совместно. Хотя эти управляющие секции раздельно обрабатываются ассемблером и загрузчиком, однако в оверлейной структуре они рассматриваются как одно целое. Например, D может содержать исполняемые команды, в то время как Е - связанные с ними данные. Другой пример - E может содержать часто используемые в D подпрограммы. На рис.3.12в показана длина (в шестнадцате-ричном виде) каждой управляющей секции.

Бек. Введение в системное программирование. 1988 8815110
Рис.3.12. Пример оверлейной программы.

Большинство систем, поддерживающих оверлейные программы, требует, чтобы они имели древовидную структуру (т.е. такую, как на рис.3.12а). Узлы такого дерева называются сегментами. Корневой сегмент загружается в самом начале исполнения программы и остается в памяти до окончания счета. Остальные сегменты загружаются тогда, когда к ним происходит обращение. Предположим, например, что управляющая секция A вызывает B. Этот вызов приведет к автоматической загрузке сегмента, содержащего B (если только он уже не находится в оперативной памяти). Если позднее А вызовет D, то будет загружен сегмент, содержащий секцию D. Порядок расположения сегментов, находящихся на одном уровне оверлейной структуры, значения не имеет. Например, А может сначала вызвать С, затем В, затем D/E, потом вновь С и т.д.

Предположим, что в некоторый момент времени исполняется управляющая секция H. Она была вызвана секцией B, которую в свою очередь вызвала секция A. Таким образом, эти три сегмента являются активными и должны находиться в оперативной памяти. Другие сегменты не могут быть активными, поскольку нет путей, связывающих их с H. Например, если ранее был вызван сегмент, содержащий K, то он должен был вернуть управление D/E (и затем A) прежде, чем B мог быть вызван из секции A. Данные рассуждения приводят нас к правилу, которое используется в большинстве систем, допускающих оверлейные структуры: если сегмент S находится в оперативной памяти, то все другие сегменты, расположенные на пути, ведущем из S к корню, также должны находиться в оперативной памяти. Соблюдение этого правила упрощает организацию оверлея. Если некоторый сегмент вызывает сегмент, расположенный на более низком уровне древовидной структуры (т.е. находящийся дальше от корня), то может потребоваться загрузка этого сегмента в оперативную память. С другой стороны, если некоторый сегмент ссылается на сегмент, лежащий между ним и корнем, то этот сегмент уже находится в памяти.

Поскольку сегменты одного уровня (например, B, C и D/E) могут вызываться только из сегмента вышестоящего уровня, то они не требуются, одновременно. Поэтому они могут совместно использовать одну и ту же область оперативной памяти. Если передача управления вызывает загрузку некоторого сегмента, то он ЗАМЕЩАЕТ в памяти сегмент, который находится на одном с ним уровне (и сегменты ему подчиненные). Таким образом, общий объем оперативной памяти, требуемой для исполнения программы, оказывается меньше. Это и является основной причиной использования оверлейных структур.

Оверлейная структура программы описывается при помощи команд загрузчика, похожих на те, что мы рассматривали в предыдущем разделе. На рис.3.13 приведен набор команд, который задает оверлейную структуру, изображенную на рис.3.12а. Команда

SEGMENT имя-сегмента (управляющая-секция...)

определяет сегмент (т.е. узел древовидной структуры). Данная команда задает имя сегмента и описывает список входящих в него управляющих секций. Первый определяемый сегмент - это корень.. Два последовательных предложения SEGMENT задают между описываемьши сегментами отношение вида отец - сын. Поэтому на рис.3.13 первые три предложения описывают самый левый путь (от корня к F/G) структуры на рис.3.12а.

Бек. Введение в системное программирование. 1988 8815310
Рис.3.13. Определения оверлейной структуры с помощью управляющих предложений.

Остальные сегменты, имеющие общего отца, на рис.3.13 задаются с помощью предложений другого типа. Предложение

PARENT имя-сегмента

описывает сегмент (ранее определенный), который является отцом следующего за ним сегмента. Таким образом, на рис.3.13 первое предложение PARENT определяет, что SEG2 является отцом SEG4. Если бы этого предложения не было, то отцом SEG4 считался бы сегмент SEG3.

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

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

Бек. Введение в системное программирование. 1988 8815410
Рис.3.14. Распределение памяти для оверлейной программы.

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

Собственно оверлейный процесс (т.е. загрузка сегмента, когда ему передается управление) может осуществляться различными способами. Здесь мы опишем простой механизм, который не требует вмешательства операционной системы. Наряду с ним могут использоваться средства, аналогичные аппарату динамического связывания (см. разд.3.4.2).

Фактическая загрузка сегментов в процессе исполнения программы осуществляется специальной сопрограммой - МЕНЕДЖЕРОМ ОВЕРЛЕЯ (overlay manager). В нашем примере менеджер оверлея, расположен в отдельной управляющей секции с именем OVLMGR. Эта секция автоматически включается загрузчиком в корневой сегмент оверлейной программы. Для управления загрузкой сегментов OVLMGR должен иметь информацию об оверлейной структуре программы. Эта информация хранится в таблице сегментов (SEGTAB), которая создается загрузчиком и включается в корневой сегмент в виде отдельной управляющей секции. Таблица SEGTAB описывает древовидную структуру, определяя уровень расположения каждого из сегментов. Для каждого сегмента она также содержит его начальный адрес загрузки, адрес входной точки и месторасположение сегмента в SEGFILE. (В данной реализации мы полагаем, что сегмент может вызываться только по одной входной точке).

Кроме того, для каждого сегмента, за исключением корня в SEGTAB, имеется ОБЛАСТЬ ПЕРЕДАЧИ УПРАВЛЕНИЯ. Она содержит команды, которые обеспечивают передачу управления на требуемый сегмент. Если сегмент уже загружен в оперативную память, то его область передачи управления содержит простую команду перехода на входную точку сегмента. Если сегмент не загружен, то область передачи управления содержит команды активизации OVLMGR и передачи ему информации, необходимой для загрузки сегмента. В процессе своей работы менеджер оверлея изменяет команды в области передачи управления так, чтобы они всегда соответствовали текущему статусу каждого из сегментов. Обращение одного сегмента к другому, стоящему на следующем уровне древовидной структуры (т.е. обращение, которое может потребовать оверлейную загрузку), преобразуется загрузчиком в обращение к области передачи управления соответствующего сегмента. Область передачи управления всегда присутствует в оперативной памяти, так как SEGTAB расположена в корневом сегменте. Затем команды области передачи управления либо непосредственно передают управление на требуемый сегмент, либо активизируют OVLMGR. В последнем случае OVLMGR загружает требуемый сегмент, обновляет SEGTAB и после этого передает управление сегменту.

Иллюстрация описанного процесса дана на рис.3.15. На рис.3.15б показано содержимое оперативной памяти на некотором этапе исполнения программы. В данный момент загружены Сегменты 1, 2, 4 и исполняются команды управляющей секции A. Сегменты 2 и 4 были загружены в ответ на ранее выполненные команды вызова. Хотя сейчас управление вернулось Сегменту 1, однако Сегменты 2 и 4 продолжают оставаться в оперативной памяти до тех пор, пока их не заменят другие сегменты. Области передачи управления Сегментов 2 и 4 содержат команды перехода на управляющие секции B и H. Остальные области передачи управления содержат команды активизации OVLMGR (соответствующие им поля SEGTAB заштрихованы). Если в управляющей секции А сейчас будет выполнена команда вызова секции В (в исходной программе команда -j-JSUB В), то в результате выполнится переход в область передачи управления Сегмента 2. Как показано на рисунке, это приведет к прямой передачи управления на секцию В.

Бек. Введение в системное программирование. 1988 8815610 Бек. Введение в системное программирование. 1988 88156a10
Рис.3.15. Пример управления оверлеем.

Предположим теперь, что A вызывает D. На рис.3.15 показано, что область передачи управления, соответствующая Сегменту 6, содержит команды активизации OVLMGR. Менеджер оверлея загрузит Сегмент 6 из SEGFILE на выделенное ему место в оперативной памяти. Затем он обновит SEGTAB, указав, что в данный момент Сегмент 6 находится в памяти, а Сегменты 2 и 4 нет. Заметим, что Сегмент 4 должен быть удален из памяти (даже если занятое им пространство не требуется для нового сегмента), так как удален его отец. Теперь для вызова секции D остается передать управление на входную точку- Сегмента 6 (см. рис.3.15в).

Возврат управления из вызванного сегмента (например, из D в A) может выполняться обычным образом. (Активизация OVLMGR для этого не требуется). Например, команда JSUB (рис.3.15б) может занести адрес возврата в регистр L. Менеджер оверлея сохранит это значение. Таким образом, по завершению своей работы управляющая секция D может просто выполнить команду RSUB. Это тот же самый механизм, который используется для программ без оверлейной структуры.

В некоторых системах функции OVLMGR выполняются специальным блоком операционной системы, который называется СУПЕРВИЗОРОМ ОВЕРЛЕЯ {overlay supervisor). В этом случае SEGTAB содержит макрокоманды операционной системы, обеспечивающие загрузку требуемого сегмента.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вт Фев 09, 2021 10:35 am

3.4. ВАРИАНТЫ ПОСТРОЕНИЯ ЗАГРУЗЧИКОВ

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

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

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

В разд.3.4.3 мы обсудим РАСКРУЧИВАЮЩИЕ ЗАГРУЗЧИКИ (bootstrap loaders). Такие загрузчики могут использоваться для программ, выполняемых без операционной системы или системного загрузчика. Они также могут применяться для загрузки в оперативную память самой операционной системы или системного загрузчика.

3.4.1. РЕДАКТОРЫ СВЯЗЕЙ

Основное различие между редактором связей и связывающим загрузчиком показано на рис.3.16. Вначале исходный текст переводится ассемблером или компилятором в объектную программу (которая может состоять из нескольких управляющих секций). Связывающий загрузчик выполняет все операции связывания и перемещения, включая, если это необходимо, и автоматический библиотечный поиск, и загружает подготовленную для исполнения программу непосредственно в оперативную память. С другой стороны, редактор связей подготавливает вариант программы с разрешенными внешними связями (его часто называют ЗАГРУЗОЧНЫМ МОДУЛЕМ или ИСПОЛНЯЕМОЙ ПРОГРАММОЙ) и записывает его в файл или в библиотеку.

Бек. Введение в системное программирование. 1988 8815810
Рис.3.16. Обработка объектной программы с использованием связывающего загрузчика (а) и редактора связей (б).

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

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

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

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

Кроме подготовки объектной программы редактор связей может выполнять и много других полезных функций. Рассмотрим, например, программу (PLANNER), которая использует большое количество подпрограмм. Предположим, что в одну из этих подпрограмм (PROJECT) внесены изменения (например, для исправления ошибок или повышения эффективности). После ассемблирования или компилирования новой версии PROJECT мы должны включить ее в PLANNER. Редактор связей позволяет сделать это, не возвращаясь к исходным (раздельным) версиям всех программ. Ниже приведена последовательность команд редактора связей, которая позволяет выполнить эту работу. Используемый здесь язык аналогичен тому, что мы рассматривали в разд.3.3.2:

INCLUDE PLANNER(PROGLIB)
DELETE PROJECT {Исключить из старой версии}
INCLUDE PROJECT(NEWLIB) {Включить новую версию}
REPLACE PLANNER(PROGLIB)

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

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

INCLUDE READR(FTNLIB)
INCLUDE WRITER(FTNLIB)
INCLUDE BLOCK(FTNLIB)
INCLUDE DEBLOCK(FTNLIB)
INCLUDE ENCODE(FTNLIB)
INCLUDE DECODE(FTNLIB)
...
SAVE FTNIO(SUBLIB)

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

Редакторы связей часто разрешают пользователю указывать те внешние ссылки, которые не должны обрабатываться с помощью автоматического библиотечного поиска. Предположим, например, что в библиотеке должно храниться 100 Фортран-программ, в которых используются описанные выше подпрограммы ввода-вывода. Если все эти программы хранить с полностью разрешенными внешними связями, то в библиотеке будет записано 100 копий FTNIO. Если библиотечное пространство дорого, то это может оказаться крайне нежелательным. В этом случае пользователь может с помощью команд, похожих на те, что были рассмотрены в разд.3.3.2, запретить выполнение библиотечного поиска во время работы редактора связей. Тогда будут разрешены только внешние связи между пользовательскими подпрограммами. Впоследствии во время исполнения программы для объединения пользовательской программы с модулем FTNIO можно будет использовать связывающий загрузчик. Поскольку в этом случае потребуется выполнить две отдельные операции связывания, то это приведет к некоторому увеличению накладных расходов, но зато позволит сэкономить библиотечное пространство.

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

3.4.2. ДИНАМИЧЕСКОЕ СВЯЗЫВАНИЕ

Редакторы связей выполняют операции связывания до того, как программа загружается для исполнения. Связывающий загрузчик выполняет эти же операции во время загрузки программы. Здесь мы рассмотрим схему загрузки, в которой связывание откладывается до момента исполнения программы. В этом случае загрузка и связывание подпрограммы осуществляются тогда, когда к ней происходит первое обращение. Такая организация процесса загрузки обычно называется ДИНАМИЧЕСКИМ СВЯЗЫВАНИЕМ (dynamic linking) или ДИНАМИЧЕСКОЙ ЗАГРУЗКОЙ [dynamic loading).

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

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

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

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

Бек. Введение в системное программирование. 1988 8816310
Рис.3.17. Загрузка и вызов подпрограмм с помощью динамического связывания.

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

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

3.4.3. РАСКРУЧИВАЮЩИЕ ЗАГРУЗЧИКИ

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

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

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

Промежуточное решение заключается в том, чтобы иметь встроенные аппаратные средства (или очень короткую программу в ПЗУ), которые позволяют прочитать запись фиксированной длины с некоторого внешнего запоминающего устройства в фиксированное место оперативной памяти. Используемое конкретное устройство обычно можно задать с помощью пультовых переключателей. После того как запись будет прочитана в память, управление передается на ее начало. Считанная запись содержит команды загрузки абсолютного загрузчика. Если команды, требуемые для организации процесса загрузки, не помещаются в одну запись, то первая запись может служить для чтения еще одной записи, а та в свою очередь может прочитать еще и другие записи. Первая запись (или записи) обычно называется РАСКРУЧИВАЮЩИМ ЗАГРУЗЧИКОМ (bootstrap loader). Такие загрузчики должны находиться в начале любой объектной программы, которая предназначена для работы на пустой машине. К числу таких программ относятся, например, сами операционные системы и все другие программы, которые должны выполняться без операционной системы.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вт Фев 09, 2021 10:40 am

3.5. ПРИМЕРЫ РЕАЛИЗАЦИИ

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

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

3.5.1. РЕДАКТОР СВЯЗЕЙ SYSTEM/370

Формат объектной программы, обрабатываемой редактором связей System/370, очень похож на тот, что мы обсуждали в связи с УУМ/ДС. Для повышения эффективности используется механизм ссылочных номеров, рассмотренный нами в разд. 3.2.3. Выходная программа редактора связей называется ЗАГРУЗОЧНЫМ МОДУЛЕМ (load module). Загрузочные модули могут загружаться в оперативную память и исполняться. Они также (обычно) содержат информацию, необходимую для их последующей обработки с помощью редактора связей. Однако пользователь может указать, что некоторый модуль является "нередактируемым", и в этом случае большая часть управляющей информации будет опущена. Это позволяет делать более компактные загрузочные модули.

Редактор связей System/370 может выполнять все обсуждавшиеся нами стандартные операции. Управляющие секции можно исключать, заменять или реорганизовывать. Имена, используемые во внешних ссылках, можно заменять или исключать. Для облегчения редактирования обеспечивается автоматическая замена управляющих секций. Если две или более обрабатываемые управляющие секции имеют одинаковые имена, то только первая из них будет включена в загрузочный модуль. Другие будут исключены, и это не рассматривается как ошибка. Для обработки внешних ссылок редактор связей выполняет автоматический библиотечный поиск в системных и пользовательских библиотеках. Однако пользователь может запретить такой поиск для некоторых или даже для всех внешних ссылок.

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

Редактор связей System/370 поддерживает оверлейные программы, структура которых совпадает с той, что мы рассматривали в разд.3.3.3, и даже предоставляет более широкие возможности. В каждом сегменте разрешается использовать несколько входных точек. Для этого в каждый сегмент, который может потребовать оверлейную загрузку, автоматически включается ТАБЛИЦА ВХОДОВ (entry table). В этой таблице для каждого внешнего имени указан соответствующий ему сегмент. Имеется также и таблица сегментов (она похожа на таблицу сегментов, описанную нами в разд.3.3.3). В оверлейных программах при некоторых ограничениях разрешено использовать так называемые ИСКЛЮЧИТЕЛЬНЫЕ ССЫЛКИ (exclusive references) - ссылки, которые требуют оверлейного замещения самого вызывающего сегмента. Примером такой ссылки мог бы служить вызов сегмента С из сегмента В в программе на рис.3.12а. Однако пользоваться этой возможностью рекомендуется только в особых случаях. В стандартном режиме (если не задан соответствующий атрибут) такие ссылки рассматриваются редактором связей как ошибочные.

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

В System/370 оверлейные программы могут разбиваться на несколько различных ОБЛАСТЕЙ, что позволяет более эффективно использовать оперативную память. Каждая область содержит древовидную оверлейную структуру. Внутри одной области применяются обычные правила организации оверлея. Однако области не зависят друг от друга, и поэтому сегмент одной области может свободно обращаться к любому сегменту другой области.

Рассмотрим, например, структуру на рис.3.18а. Это та же самая оверлейная структура, что и на рис.3.12а, но с добавленными управляющими секциями L, M и N. Каждая из них может вызываться либо из секции Н, либо из секции J. Эта структура является недопустимой, так как L, M и N размещены в двух различных узлах дерева, что приводит к неоднозначному определению внешних имен. Для того чтобы организовать допустимую оверлейную структуру, расположенную в одной области, нам надо было бы разместить L, M и N в корневом сегменте. В этом случае они были бы доступны как H, так и J (рис.3.18б). Однако это означало бы, что все эти три управляющие секции оставались бы в оперативной памяти в течение всего времени исполнения программы. Таким образом, преимущество оверлейной структуры было бы утрачено. Если L, M и N велики по объему, то это привело бы к значительному увеличению размера занимаемой оперативной памяти.

Бек. Введение в системное программирование. 1988 8816810
Рис.3.18 Оверлейная программа, расположенная в нескольких областях.

На рис. 3.18в эта проблема решена с помощью размещения оверлейной структуры в нескольких областях. Область 1 содержит ту же самую оверлейную структуру, которая приведена на рис.3.12а. Область 2 содержит сегменты L, M и N. Эти сегменты подключены к фиктивному корню для того, чтобы подчеркнуть, что они могут друг друга замещать. Это позволяет решить проблему эффективного использования оперативной памяти и в то же время позволяет обращаться к любому сегменту Области 2 как из сегмента H, так и из сегмента J.

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

Более подробную информацию о редакторе связей System/370 можно найти в IBM [1978] и IBM [1972a].

3.5.2. ПРОГРАММА СВЯЗЫВАНИЯ ЭВМ VAX

Редактор связей в системе VAX называется ПРОГРАММОЙ СВЯЗЫВАНИЯ (linker) и выполняет функции, аналогичные тем, что мы уже обсуждали в этой главе. Формат объектной программы VAX несколько более сложный, чем формат, использовавшийся для УУМ/ДС. Результатом работы программы связывания является одна или несколько БЛОК-СЕКЦИЙ (image sections). Блок-секция состоит из нескольких программных секций (PSECT), имеющих сходные значения атрибутов (например, таких как защита по записи или исполнению).

Работа программы связывания по созданию блок-секций управляется ассемблером или компилятором с помощью специальных команд, включаемых в объектную программу. (Запись-модификатор, являющаяся частью объектной программы УУМ/ДС, представляет собой простой пример команды этого типа). В качестве рабочей памяти программа связывания использует внутренний стек. Команды объектной программы могут заносить в стек разнообразную информацию, считывать значения из стека и записывать их в блок-секцию, а также выполнять операции над величинами, записанными в стек. Набор команд состоит более чем из 50 различных операций, обеспечивающих самые широкие возможности.

На рис.3.19 приведен простой пример, иллюстрирующий данный способ организации перемещения и связывания. На рис.3.19а показаны три программные секции, ассемблировавшиеся совместно. По существу это те же программные секции, которые использовались нами в примере на рис.3.7. Однако здесь показана только та часть, которая необходима для обработки предложения с меткой REF4 (из секции PROGA). На рис.3.19б приведен несколько упрощенный вариант команд, обеспечивающих генерацию значения предложения REF4.

Бек. Введение в системное программирование. 1988 8817110
Рис.3.19. Пример использования механизма перемещения и связывания ЭВМ VAX.

Первая команда заносит в вершину стека число 14 (шестнадцатеричное), являющееся значением выражения (ENDA-LISTA). Вторая команда заносит в стек базовый адрес секции PROGC (он назначается программой связывания) плюс смещение 30 (шестнадцатеричное). Эта сумма представляет собой значение метки LISTC. Третья команда суммирует два верхних элемента стека и помещает результат в его вершину. Последняя команда запоминает эту величину в качестве следующего слова генерируемой блок-секции. Это как раз то значение, которое должно содержаться в слове с меткой REF4.

Программа связывания VAX может генерировать три типа блок-секций. Блок-секции первого типа - это ВЫПОЛНЯЕМЫЕ СЕКЦИИ (executable images), используемые для загрузки и исполнения. Однако их последующая обработка программой связывания невозможна. Второй тип блок-секций - это РАЗДЕЛЯЕМЫЕ СЕКЦИИ (shareable images). Такие секции исполняться не могут, но зато они могут впоследствии обрабатываться программой связывания. Их можно, например, использовать в качестве промежуточного представления для очень больших программ. Разделяемые блок-секции позволяют также различным программам использовать одру и ту же копию некоторого набора команд или областей данных. На магнитном диске и в оперативной памяти присутствует только один экземпляр разделяемой блок-секции, что дает возможность экономить эти ресурсы.

Третий тип - это СИСТЕМНЫЕ БЛОК-СЕКЦИИ (system images). Такая блок-секция предназначена для работы на машине без поддержки операционной системы. (Операционная система VAX сама является системной блок-секцией). Эти блок-секции используются только для специальных целей. По сравнению с другими типами блок-секций системная блок-секция имеет более простую структуру.

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

Ссылки между раздельно ассемблируемыми программными секциями обрабатываются так, как было описано в разд.3.2. Используемые имена должны быть объявлены ГЛОБАЛЬНЫМИ (т.е. внешними). Определения глобальных имен являются составной частью объектного представления и используются программой связывания для разрешения внешних ссылок между программными секциями. Различают два типа внешних ссылок - СИЛЬНЫЕ (strong) и СЛАБЫЕ (weak). Сильные ссылки обрабатываются обычным образом. Разрешение слабых ссылок осуществляется только в том случае, если секция, в которой определены соответствующие имена, присутствует во входном потоке программы связывания. Автоматический библиотечный поиск для этих ссылок не делается. Неразрешенные слабые ссылки не рассматриваются как ошибки. Вместо этого им присваивается нулевое значение. Одно из применений таких слабых ссылок - тестирование модульных программ. В этом случае может возникнуть желание протестировать часть программы прежде, чем будут написаны все подпрограммы. Если все вызовы к отсутствующим подпрограммам идентифицировать как слабые ссылки, то программу можно будет беспрепятственно загрузить и исполнять. Конечно, если программа попытается вызвать отсутствующую подпрограмму, то в результате будет зафиксирована ошибка. (Редактор связей System/370 и загрузчик ЭВМ CYBER допускают использование похожих слабых ссылок).

Определения глобальных переменных обычно отсутствуют в исполняемой блок-секции, поскольку эта секция не предназначена для повторной обработки с помощью программы связывания. Однако в случае использования некоторых средств отладки глобальные имена остаются в исполняемой блок-секции и применяются при выводе диагностических сообщений. В разделяемой блок-секции определения некоторых глобальных имен сохраняются для того, чтобы обеспечить возможность связывания этой секции с другими программами. Эти имена, называемые ОБЩИМИ (universal) именами, задаются с помощью команд программы связывания. Глобальные имена, не объявленные общими, в разделяемой блок-секции не сохраняются.

Программа связывания ЭВМ VAX не поддерживает оверлейные программы. Частично это вызвано тем, что в системе VAX предоставляется виртуальная память большого объема. Разработчики системы сочли, что большой объем виртуальной памяти и применение алгоритмов управления этой памятью делают ненужным использование оверлейных структур. (Напомним, однако, что в System/370 редактор связей поддерживает оверлейные программы и на системах с виртуальной памятью).

Дополнительную информацию по системе VAX вы можете найти в DEC [1982] и DEC [1978].

3.5.3. ЗАГРУЗЧИК ЭВМ CYBER

Формат объектной программы, используемый в системе CYBER, отчасти более сложный, чем формат УУМ/ДС. Однако он содержит ту же самую базовую информацию. Для задания информации о связывании используется механизм, сходный с рассмотренными нами записями-модификаторами. Родственный механизм может быть использован для описания требуемых перемещений. Наряду с этим для описания перемещений может использоваться аппарат, похожий на аппарат масок перемещения, рассмотренный нами в разд.3.2.1. Маски перемещения особенно полезны в системе CYBER, поскольку в ней нет относительной адресации. Это означает, что в общем случае программы CYBER содержат намного больше величин, требующих перемещения, чем аналогичные программы для VAX или System/370.

В каждом слове CYBER может храниться не одна команда, а несколько. Поэтому в маске перемещения недостаточно иметь по одному разряду на каждое слово. Поскольку в CYBER адреса оперативной памяти могут присутствовать только в командах, использующих 30-разрядный формат, то имеется пять возможных вариантов размещения перемещаемой величины внутри слова:
1. перемещение не требуется;
2. перемещаемое значение находится в левой половине слова;
3. перемещаемое значение находится в правой половине слова;
4. перемещаемые значения находятся в обеих половинах слова;
5. перемещаемое значение находится в середине 30-разрядного слова.

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

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

Имеется также и более мощное средство организации оверлейного процесса, которое называется СЕГМЕНТАЦИЕЙ (segmentation). Сегментированная программа также должна иметь древовидную структуру, однако допускается использование более трех уровней и каждый сегмент может иметь несколько входных точек. Разрешается, кроме того использовать оверлейные программы, размещенные в нескольких областях памяти. Такая организация оверлейной структуры похожа на то, что мы рассматривали для System/370 (хотя и используется другая терминология). Программист с помощью командного языка может явно задать древовидную структуру, указав, какие программы должны включаться в соответствующие сегменты. Если это не сделано, то загрузчик распределяет программы между сегментами, исходя из анализа существующих между ними внешних связей.

В дополнение к стандартным функциям загрузчик CYBER позволяет создавать так называемые КАПСУЛЫ (capsules). Капсула состоит из одной или нескольких объектных программ, связанных между собой и записанных в специальном формате, позволяющем осуществлять быструю загрузку. Для того чтобы загрузить капсулу в оперативную память, прикладные программы могут вызвать быстрый динамический загрузчик (Fast Dynamic Loader - FDL). Загрузка с помощью FDL более эффективна, чем другие методы загрузки, и требует меньшие ресурсов оперативной памяти, однако она накладывает более жесткие ограничения и сложнее в использовании.

Дополнительную информацию о загрузчике CYBER можно найти в CDC [1982б].
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Вт Фев 09, 2021 10:42 am

УПРАЖНЕНИЯ

РАЗДЕЛ 3.1
1. Определите двоичный формат объектной программы для УУМ и напишите абсолютный загрузчик (на языке ассемблера УУМ), позволяющий загружать программы в этом формате.

2. Опишите метод преобразования объектной программы, формат которой использует символьное представление ассемблированного кода (см. рис.3.1а) в машинное представление. Как бы вы реализовали его на языке ассемблера УУМ?

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

РАЗДЕЛ 3.2
1. Модифицируйте алгоритм на рис.3.10 таким образом, чтобы он выполнял настройку относительных адресов при помощи масок перемещения. Связывание по-прежнему выполняется с помощью записей-модификаторов.

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

3. Примените алгоритм, показанный на рис.3.10, для связывания и загрузки объектной программы на рис.3.8. Сравните ваш результат с тем, что изображено на рис.3.9.

4. Предположим, что PROGA, PROGB и PROGC те же, что и на рис.3.9. Покажите, как изменится объектная программа (включая записи тела программы и записи-модификаторы), если в каждую программу добавить следующие предложения:

REF9 WORD LISTC
REF10 WORD LISTB-3
REF11 WORD LISTA+LISTB
REF12 WORD ENDC-LISTC-100
REF13 WORD LISTA-LISTB-ENDA+ENDB

5. Примените алгоритм, показанный на рис.3.10, для связывания и загрузки модифицированной объектной программы, сгенерированной вами в упр.4.

6. Включите в алгоритм на рис.3.10 средства для обнаружения в выражениях некорректного использования внешних имен. (Список правил см. в разд.2.3.5). Какие проблемы возникают при проведении подобных проверок?

7. Модифицируйте алгоритм на рис.3.10 так, чтобы в нем использовались номера ссылок, как это было описано в разд.3.2.3.

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

LDA XYZ

(где XYZ - внешняя ссылка) ассемблировалась бы так, как если бы она имела вид

LDA @PXYZ

где PXYZ - указатель на XYZ. В чем преимущества и недостатки этого метода?

9. Предложите проект ОДНОПРОСМОТРОВОГО связывающего загрузчика. Какие ограничения (если они нужны) требуется наложить в этом случае? В чем преимущества и недостатки такого однопросмотрового загрузчика?

10. Некоторые языки программирования разрешают размещать данные в общих областях памяти [Имеются в виду области типа COMMON языка Фортран.- Прим. ред.]. Исходная программа может содержать несколько общих областей (с разными именами). Мы можем рассматривать каждую общую область как отдельную управляющую секцию.

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

Как мог бы загрузчик обрабатывать такие общие области? (Предложите способ модификации алгоритма на рис.3.10 для обеспечения работы с общими областями).

РАЗДЕЛ 3.3
1. Модифицируйте алгоритм на рис.3.10 так, чтобы он для разрешения внешних ссылок обеспечивал автоматический библиотечный поиск. Вы можете считать, что доступ к библиотекам осуществляется с помощью сервисных процедур операционной системы.

2. Модифицируйте алгоритм на рис.3.10 так, чтобы он позволял обрабатывать команды CHANGE, DELETE и INCLUDE, описанные в разд.3.3.2. Если потребуется, вы можете ввести необходимые ограничения на их использование.

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

4. Рассмотрим оверлейную структуру, показанную на рис.3.12а. Предположим, что подпрограмма H может быть вызвана не только из B, не и из C. Как это можно реализовать в оверлейной программе? (Считаем, что правила оверлея, определенные в разд.3.3.3, не меняются).

5. Опишите алгоритм назначения начальных адресов сегментам оверлейной структуры, принимая во внимание объем памяти, необходимый для размещения SEGTAB и OVLMGR.

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

7. Дайте краткое описание процедуры управления оверлеем, которую мы назвали OVLMGR. Определите необходимые для нее структуры данных.

8. Предположим, что мы хотим разрешить иметь в сегментах оверлейной программы более чем одну входную точку. Например, в программе на рис.3.12а мы хотели бы непосредственно из сегмента A обращаться либо к входу D, либо к входу E. При этом независимо от того, по какому входу будет сделан вызов, должен загружаться весь сегмент D/E. Каким образом может быть реализована такая возможность - загрузчиком и (или) менеджером оверлея?

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

РАЗДЕЛ 3.4
1. Определите формат модуля, подходящий для представления связанной программы, генерируемой редактором связей. Будем считать, что связанная программа не предназначена для повторной обработки с помощью редактора связей. Опишите алгоритм перемещающего загрузчика, который можно использовать для загрузки модулей данного формата.

2. Рассмотрим следующие варианты хранения, связывания и исполнения пользовательской программы.
а) Хранится только исходная программа; при каждом запуске программы она переассемблируется и загружается связывающим загрузчиком.
б) Хранятся исходная программа и ее объектное представление; при каждом запуске программы она загружается с помощью связывающего загрузчика.
в) Хранятся исходная программа и вариант программы, подготовленный редактором связей, в котором оставлены неразрешенными ссылки на библиотечные подпрограммы; при каждом запуске программы она загружается с помощью связывающего загрузчика.
г) Хранятся исходная программа и вариант программы, подготовленный редактором связей, в котором разрешены все внешние ссылки; при каждом запуске программы она загружается с помощью связывающего загрузчика.
д) Хранятся исходная программа и вариант программы, подготовленный редактором связей, в котором разрешены все внешние ссылки и выполнена настройка относительных адресов; при каждом запуске программы она загружается с помощью абсолютного загрузчика.

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

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

4. Динамическое связывание, как оно было описано в разд.3.4.2, работает только для передачи управления. Как его можно приспособить для того, чтобы оно обеспечивало динамическую загрузку и при ссылках на данные?

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

6. Предположим, что требуется удалить из памяти динамически загруженную подпрограмму (для повторного использования пространства памяти). Будет ли предложенный вами в упр.5 метод работать в этом случае? Какие возникают здесь проблемы и как они могут быть разрешены?

7. Предположим, что нажатие на пульте УУМ/ДС кнопки "старт системы" вызывает чтение с вводного устройства 128-разрядной записи, которая размещается в оперативной памяти, начиная с адреса 0000. После того как запись прочитана, управление автоматически передается по адресу 0000. Какие команды должны находиться в первой раскручивающей записи для того, чтобы можно было загрузить следующую за ней абсолютную объектную программу? Для раскручивающей записи и объектной программы вы можете выбрать любой удобный вам формат.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Ср Фев 10, 2021 1:05 pm

ГЛАВА 4. МАКРОПРОЦЕССОРЫ

В этой главе мы рассмотрим функции и способы реализации макропроцессоров. Макроинструкции представляют собой удобное средство записи часто используемых групп предложений исходного языка программирования. Процесс замены макроинструкций соответствующими группами предложений, осуществляемый макропроцессором, называется МАКРОРАСШИРЕНИЕМ, РАСШИРЕНИЕМ МАКРОИНСТРУКЦИЙ или МАКРОГЕНЕРАЦИЕЙ. Таким образом, макроинструкции позволяют программисту записать компактный вариант своей программы, оставляя все технические детали, связанные с получением окончательного текста, макропроцессору.

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

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

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

В разд.4.3 описаны некоторые дополнительные возможности макропроцессоров. Одна из них (рекурсивная макрогенерация) оказывается связанной с внутренней структурой макропроцессора; другие относятся к связям макропроцессора с такими компонентами системного обеспечения, как ассемблер или компилятор.

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

4.1. ОСНОВНЫЕ ФУНКЦИИ МАКРОПРОЦЕССОРОВ

В этом разделе мы рассмотрим общие для всех макропроцессоров функции. В разд.4.1.1 обсуждаются макроопределения, процессы макровызова и макрогенерации с учетом параметров. В иллюстрирующих примерах используется язык ассемблера УУМ/ДС. В разд.4.1.2 описываются однопросмотровый алгоритм работы простого макропроцессора и необходимые для его работы структуры данных. В последующих разделах этой главы обсуждаются вопросы реализации некоторых дополнительных возможностей.

4.1.1. МАКРООПРЕДЕЛЕНИЯ И МАКРОРАСШИРЕНИЯ

На рис.4.1 изображен пример программы для УУМ/ДС, в которой используются макроинструкции. Эта программа выполняет те же функции и имеет ту же логику, что и фрагмент программы, изображенной на рис.2.5. Изменен только способ нумерации предложений.

Бек. Введение в системное программирование. 1988 8818110
Рис.4.1. Использование макросов в программе для УУМ/ДС.

В этой программе определены и используются две макроинструкции RDBUFF и WRBUFF. Функции и логика макроопределения RDBUFF те же, что и у подпрограммы RDREG, изображенной на рис.2.5; макроопределение WRBUFF аналогично подпрограмме WRREG. Эти макроопределения находятся в исходной программе непосредственно после предложения START.

В них используются две новые директивы ассемблера - MACRO и MEND. Первое предложение MACRO (строка 10) идентифицирует начало макроопределения. Текст в поле метки (RDBUFF) является именем этого макроопределения. В поле операнда находятся имена формальных параметров макроопределения. В нашем макроязыке имена всех формальных параметров начинаются спецсимволом & (амперсанд), что позволяет осуществлять замену формальных параметров на фактические в процессе макрогенерации, Имя и параметры макроса определяют шаблон или прототип используемой программистом макроинструкции. После директивы MACRO следуют предложения, составляющие тело макроопределения (строки с 15 по 90). Именно эти предложения и будут порождены в процессе макрогенерации. Директива ассемблера MEND (строка 95) является признаком конца макроопределения. Определение макроса WRBUFF (строки с 100 по 160) имеет аналогичную структуру.

Основная программа начинается со строки 180. Предложение в строке 190 является предложением макроинициализации. Оно определяет имя макроинструкции, которая должна быть инициализирована, и аргументы (фактические параметры), которые должны быть использованы в процессе макрогенерации при порождении макрорасширения. (Предложение макроинициализации часто называют также предложением макровызова или просто макровызовом. Для того чтобы избежать путаницы с предложениями вызова процедур и подпрограмм, мы предпочитаем использовать термин "макроинициализация" [Этот введенный автором термин не является общепринятым в нашей литературе, так же, впрочем, как и термин "макропрототип", обозначающий заголовок макроопределения.- Прим. ред.]. Как мы увидим, процесс макроинициализации совершенно отличен от процесса вызова подпрограммы).

Читатель должен сравнить логику головной программы на рис.4.1 с логикой программы на рис.2.5, помня о схожести функций RDBUFF и RDREG, WRBUFF и WRREG.

Программа на рис.4.1 может быть подана на вход макропроцессора. На рис.4.2 изображена результирующая программа. Определения макроинструкций в ней отсутствуют, поскольку после порождения макрорасширений они уже не нужны. Каждое предложение макроинициализации превратилось в предложения видоизмененного тела макроопределения, в котором формальные параметры, описанные в макропрототипе (заголовке макроопределения), заменены аргументами предложения макроинициализации. Между аргументами и параметрами имеется позиционное соответствие: первый аргумент в предложении макроинициализации соответствует первому параметру в макропрототипе и т.д. Например, при расширении предложения макроинициализации в строке 190 параметр &INDEV всюду, где он встречается в теле макроопределения, заменен на аргумент F1. Аналогично &BUFADR заменен на BUFFER и &RECLTH заменен на LENGTH.

Бек. Введение в системное программирование. 1988 8818310
Рис.4.2. Программа на рис.4.1 с макрорасширениями.

Строки 190а-190l представляют собой полное расширение инструкции макроинициализации в строке 190. Строки комментариев внутри тела макроопределения удалены, однако комментарии внутри предложений языка остались. Обратите внимание, что сами предложения макроинициализации вставлены как строки комментария. Они служат для напоминания об исходных предложениях, написанных программистом. Метка предложения макроинициализации (CLOOP) оставлена в качестве метки первого предложения сгенерированного макрорасширения. Это позволяет программисту использовать макроинструкции аналогично другим инструкциям языка ассемблера. Расширение предложений макроинициализации в строках 210 и 220 осуществляется аналогично. Заметьте, что в двух макроинициализациях макроса WRBUFF заданы разные аргументы и в результате получены различные расширения.

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

Сравнение расширенной программы на рис.4.2 с программой на рис.2.5 показывает наиболее существенные различия между макроинициализацией и вызовом подпрограммы. В программе на рис.4.2 предложения из тела макроса WRBUFF сгенерированы дважды: строки 210а-210h и 220а-220h. В программе на рис.2.5 соответствующие предложения имеются лишь в единственном экземпляре в подпрограмме WRREG (строки 210-240). Это является общим правилом. Предложения, составляющие расширение макроса, генерируются (и соответственно ассемблируются) каждый раз, когда соответствующий макрос инициализируется. Предложения подпрограммы имеются лишь в единственном экземпляре независимо от того, сколько раз эта подпрограмма вызывается.

Заметьте также, что наши макроинструкции были написаны таким образом, что тело макроопределения не содержало меток. На рис.4.1 в строке 140 находится предложение "JEQ *-3", в строке 155 - предложение "JLT *-14". Соответствующими предложениями в подпрограмме WRREC (рис.2.5) являются "JEQ WLOOP" и "JLT WLOOP", где WLOOP - метка TD инструкции, проверяющей состояние устройства вывода. Если бы эта метка была в строке 135 тела макроопределения, она была бы сгенерирована дважды: в строках 210b и 220d (рис.4.2). Это привело бы к ошибке (дважды определенная метка) во время работы ассемблера. Для того чтобы избежать дублирования меток, мы исключили их из тел наших макроопределений.

Использование предложений, подобных "JLT *-14", считается признаком плохого стиля программирования. Их применение отчасти может быть оправданно внутри тела макроопределения, однако и это можно считать плохим стилем, увеличивающим вероятность появления ошибок. В разд.4.2.2 мы обсудим, как можно избежать подобных конструкций.

4.1.2. МАКРОПРОЦЕССОР. ТАБЛИЦЫ И ЛОГИКА

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

Подобного рода определение одних макросов с помощью других может быть иногда полезно. Рассмотрим в качестве примера два макроопределения на рис.4.3. Тело первого макроса (MACROS) содержит предложения, определяющие RDBUFF, WRBUFF и другие макроинструкции для УУМ (рис.4.3а). Тело другого макроопределения (MACROX) содержит определения тех же макросов для УУМ/ДС (рис.4.3б). Программа, предназначенная для использования на стандартной УУМ, может инициализировать MACROS для определения необходимых ей макросов. Программа для УУМ/ДС может инициализировать MACROX для определения тех же макросов в расширенной ДС версии. Таким образом, одна и та же программа сможет работать как на стандартной УУМ, так и на УУМ/ДС (используя все ее дополнительные возможности). Единственное, что для этого надо сделать,- это инициализировать один из макросов: либо MACROS, либо MACROX. Важно понять, что само описание макроопределений MACROS или MACROX не определяет RDBUFF и других макросов. Они становятся доступными для использования только после завершения обработки предложений макроинициализации макросов MACROS или MACROX [По-видимому автор здесь хочет сказать, что одну и ту же программу можно выполнять на УУМ и на УУМ/ДС, меняя только макроопределения.- Прим. ред.].

Бек. Введение в системное программирование. 1988 8818510
Рис.4.3. Пример использования макроопределения внутри тела другого макроопределения.

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

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

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

На рис.4.4 показаны фрагменты содержимого этих таблиц на этапе обработки программы, изображенной на рис.4.1. Рис.4.4а содержит макроопределение RDBUFF, записанное в таблицу DEFTAB, на начало и конец которого смотрят соответствующие указатели из таблицы NAMTAB. Обратите внимание на позиционную нотацию, использованную для обозначения параметров: параметр &INDF превращен в ?1 (указывая на то, что это первый параметр в прототипе), параметр &BUFADR преобразован в ?2 и т.д. На рис.4.4б изображена таблица ARGTAB в том виде, в котором она окажется во время расширения макроса RDBUFF в строке 190. Для этой макроинициализации первым аргументом будет F1, вторым - BUFFER и т.д. Такая запись параметров делает процесс их замены на соответствующие аргументы существенно более эффективным. Как только встречается конструкция ?n в строке таблицы DEFTAB, за счет простых индексных операций можно выбрать соответствующий аргумент из таблицы ARGTAB.

Бек. Введение в системное программирование. 1988 8818710
Рис.4.4. Содержимое таблиц макропроцессора для программы на рис.41: а - фрагменты таблиц NAMTAB и DEFTAB, связанные с макроопределением RDBUFF, б - содержимое таблицы ARGTAB для макроинициализации RDBUFF в строке 190.

Собственно алгоритм работы макропроцессора представлен на рис.4.5. Процедура DEFINE, которая вызывается, как только распознано начало макроопределения, формирует соответствующие строки таблиц DEFTAB и NAMTAB. Процедура EXPAND записывает аргументы в таблицу ARGTAB и осуществляет расширение предложений макроинициализации. Процедура GETLINE, которая вызывается в нескольких местах алгоритма, выбирает очередную строку для обработки. Это может быть строка из таблицы DEFTAB (очередная строка тела макроопределения) или очередная строка входного файла в зависимости от того, какое значение имеет булевская переменная EXPANDING.

Бек. Введение в системное программирование. 1988 8818910
Рис.4.5. Алгоритм работы однопроходного макропроцессора.

Одна из особенностей этого алгоритма заслуживает дальнейших пояснений. Это обработка макроопределений внутри тела других макроопределений (как на рис.4.3). После того как заголовок макроопределения записан в таблицу DEFTAB, было бы естественно продолжать заполнять эту таблицу до тех пор, пока не встретилась директива MEND. Этого, однако, нельзя делать для примера, изображенного на рис.4.3, поскольку директива MEND в строке 3 (конец макроопределения RDBUFF) была бы интерпретирована как конец макроопределения MACROS. Для того чтобы обойти эту трудность, наша процедура DEFINE содержит счетчик LEVEL. Каждый раз, когда встречается MACRO, величина счетчика LEVEL увеличивается на 1; по каждой директиве MEND его величина уменьшается на 1. Нулевое значение этого счетчика означает, что встретилась директива MEND, соответствующая исходной директиве MACRO. Этот процесс во многом аналогичен анализу открывающихся и закрывающихся скобок при обработке арифметических выражений. Для того чтобы убедиться, что вы понимаете работу этого алгоритма, вы можете применить его вручную к программе на рис.4.1. Результат должен быть тот же, что и на рис.4.2.

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

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Чт Фев 11, 2021 10:26 am

4.2. МАШИННО-НЕЗАВИСИМЫЕ ОСОБЕННОСТИ МАКРОПРОЦЕССОРА

В этом разделе мы обсудим некоторые расширения описанных выше базовых функций макропроцессора. Как мы уже отмечали, эти расширения не связаны непосредственно с архитектурой машины, для которой создавался макропроцессор. В разд.4.2.1 описан метод конкатенации параметров макроинструкции с другими строками символов. В разд.4.2.2 обсуждается один из методов генерации уникальных меток, который позволяет избежать частого использования относительной адресации в теле макроопределения. В разд.4.2.3 вводится и иллюстрируется на нескольких примерах важное понятие условного макрорасширения (условной макрогенерации). Возможность изменения макрорасширения путем использования управляющих предложений делает макроинструкции существенно более мощным и полезным для программиста средством. В разд.4.2.4 обсуждается определение и использование ключевых параметров макроинструкций.

4.2.1.КОНКАТЕНАЦИЯ МАКРОПАРАМЕТРОВ

Большинство макропроцессоров допускает конкатенацию параметров с другими строками символов. Предположим, например, что программа содержит набор переменных с именами XA1, XA2, XA3, ..., другой набор переменных с именами XB1, XB2, XB3, ... и т.д. Если над подобными наборами переменных необходимо осуществить одинаковые действия, программист может записать их в виде макроопределения. Его параметр должен так определить необходимый набор переменных (например, A, B и т.д.), чтобы макропроцессор смог, используя значения этого параметра, сконструировать в процессе макрогенерации все необходимые имена (XA1, XB1 и т.д.).

Предположим, что такой параметр назван &ID. Тело макроопределения может содержать предложение вида

LDA X&ID1

в котором параметр &ID должен быть сконкатенирован со строкой, состоящей из символа X, расположенной до параметра, и со строкой, состоящей из символа 1, расположенной после параметра. Проблема состоит в том, что конец параметра никак специально не обозначен (его начало легко может быть идентифицировано по символу &). Таким образом, рассматриваемое предложение может быть интерпретировано также как строка символов X, за которой следует параметр &ID1. В данном конкретном случае макропроцессор сможет интерпретировать это предложение правильно. Однако если макроопределение содержит в качестве параметров и &ID, и &ID1, то ситуация становится в принципе неразрешимой.

Большинство макропроцессоров решает эту проблему введением специального оператора конкатенации. В макроязыке УУМ этот оператор записывается как ->. Предыдущее предложение на этом макроязыке должно будет иметь вид

LDA X&ID->1

где конец параметра &ID четко определен. Макропроцессор уничтожает все вхождения символа, обозначающего оператор конкатенации, сразу же после завершения подстановки параметров. Таким образом, символ -> не появится в порожденном тексте макрорасширения.

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

Бек. Введение в системное программирование. 1988 8819110
Рис.4.6. Конкатенация макропараметров.

4.2.2. ГЕНЕРАЦИЯ УНИКАЛЬНЫХ МЕТОК

Как мы уже говорили в разд.4.1, тело макроопределения, вообще говоря, не должно содержать обычных меток. Это приводит к необходимости использования в исходной программе относительной адресации. Рассмотрим в качестве примера определение WRBUFF на рис.4.1. Если бы команда TD в строке 135 была помечена, эта метка оказалась бы дважды определенной (для каждой инициализации WRBUFF). Это, естественно, не позволило бы ассемблеру получить готовую программу.

Поскольку строку 135 в этом макроопределении нельзя пометить, в командах перехода на строки 140 и 155 появляются относительные адреса *-3 и *-14. Использование относительной адресации в исходной программе еще может быть приемлемым для коротких переходов типа JEQ #-3. Однако для переходов к далеко расположенным командам подобная запись является неудобной, увеличивает вероятность появления ошибок, трудна для понимания. Многие макропроцессоры решают эту проблему за счет макрогенерации меток специального вида.

Рис.4.7 иллюстрирует один из способов порождения уникальных меток. Макроопределение RDBUFF изображено на рис.4.7а. Метки, используемые внутри тела макроопределения, начинаются специальным символом $. На рис.4.76 изображены предложения макроиницализации и соответствующие им тексты макрорасширений. Все идентификаторы, начинающиеся символом $, модифицированы путем замены символа $ на символы $AA. При обработке других предложений макроинциализации символ$ будет заменен на $XX, где XX - двухсимвольный алфавитно-цифровой счетчик количества обработанных предложений макроинициализации. При обработке первого встретившегося в программе предложения макроинициализации XX будет иметь значение AA. При обработке последующих предложений макроинициализации XX будет иметь значения AB, AC и т.д. (Если для счетчика XX используются только латинские буквы и цифры, то подобный двухсимвольиый счетчик позволяет обработать в одной программе до 1296 предложений макроинициализации). Таким образом, в макрорасширениях, соответствующих различным предложениям макроинициализации, метки будут различаться. Другие примеры будут изображены на рис.4.8 и 4.10.

Бек. Введение в системное программирование. 1988 8819210
Рис.4.7. Генерация уникальных меток для макрогенерации.

Язык ассемблера УУМ допускает появление символа $ в индентификаторах. Однако программисты предупреждаются о том, что этот символ не следует использовать в их программах. Это позволяет избежать всяких конфликтов между идентификаторами программиста и идентификаторами, порожденными макропроцессором.

4.2.3. УСЛОВНЫЕ МАКРОРАСШИРЕНИЯ

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

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

На рис.4.8 приведен пример использования одного из предложений условной макрогенерации. На рис.4.8a изображено макроопределение RDBUFF логика и функции которого уже обсуждались. Это макроопределение содержит два дополнительных параметра: &EOR, который определяет шестнадцатеричный код, являющийся признаком конца записи, и &MAXLTH, который определяет максимальную длину записи, которая может быть прочитана. (Как мы увидим, любой из этих параметров или оба сразу могут быть опущены при инициализации макроса RDBUFF).

Бек. Введение в системное программирование. 1988 8819510
Рис.4.8. Использование условных предложений периода макрогенерации

Предложения в строках 44-48 этого макроопределения являются примером простой условной структуры периода макрогенерации. В предложении IF вычисляется булевское выражение, являющееся его операндом. Если значение этого выражения есть TRUE, то порождаются предложения, следующие за предложением IF до тех пор, пока не встретится предложение ELSE. В противном случае эти предложения опускаются и порождаются предложения, следующие за ELSE. Предложение ENDIF завершает условную конструкцию, начатую предложением IF. (Как обычно, альтернатива ELSE может быть опущена). Таким образом, если значением параметра &MAXLTH является пустая строка (это означает, что соответствующий аргумент был опущен в предложении макроинициализации), то будет порождено предложение в строке 45. В противном случае будет порождено предложение в строке 47.

Аналогичная конструкция содержится в строках 26-28. Обратите внимание, что предложение, следующее за IF, не является строкой, которая должна быть порождена в процессе макрогенерации. Это предложение является директивой макропроцессора (SET), которая присваивает значение 1 переменной &EORCK. Подобные переменные называются переменными периода макрогенерации. Они позволяют хранить нужные значения в период порождения текста макрорасширения. Любой идентификатор, начинающийся символом & и не являющийся параметром макроопределения, является переменной периода макрогенерации. Считается, что начальное значение всех таких переменных равно нулю. Таким образом, если в нашем примере в предложении макроинициализации будет задан аргумент, соответствующий параметру &EOR, то значение параметра &EOR не является пустой строкой, и переменной &EORCK будет присвоено значение 1. В противном случае она сохранит свое исходное значение нуль. Значение этой переменной периода макрогенерации используется в условных структурах в строках с 38 по 43 и с 63 по 73.

В предыдущем примере переменная периода макрогенерации &EORCK использовалась для запоминания результата анализа параметра &EOR (строка 26). В предложениях IF (строки 38 и 63) можно было бы, конечно, вместо использования переменной &EORCK повторить исходное сравнение, в результате которого это значение было получено. Однако использование переменной периода макрогенерации оправданно здесь хотя бы уже потому, что оно указывает на то, что в обоих предложениях IF используются одинаковые условия. Кроме того, проверка значения переменной может оказаться быстрее, чем повторение исходного сравнения, особенно если оно связано с вычислением сложных булевских выражений.

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

Бек. Введение в системное программирование. 1988 8819610
Рис.4.8. (продолжение)

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

Когда в процессе макрогенерации встречается предложение IF, вычисляется соответствующее булевское выражение. Если оно истинно, то макропроцессор продолжает обрабатывать строки из таблицы DEFTAB до тех пор, пока не встретит предложение ELSE или ENDIF. Если встретилось предложение ELSE, то макропроцессор пропускает строки в таблице DEFTAB до предложения ENDIF. После предложения ENDIF макропроцессор продолжает работать в обычном режиме. Если же значение булевского выражения предложения IF ложно, то макропроцессор пропускает строки в таблице DEFTAB до тех пор, пока не встретит ELSE или ENDIF. Далее макропроцессор продолжает работать, как обычно.

Предложенная выше реализация не допускает вложенных конструкций IF. Вы можете подумать о том, как она должна быть изменена, чтобы обрабатывать подобные структуры (см.упр.4.2.8 ).

Чрезвычайно важно понять, что проверка истинности булевских выражений в предложениях IF осуществляется на этапе макрогенерации. К началу работы ассемблера все подобные решения уже приняты. Образовалась только одна последовательность предложений готовой программы (например, предложения на рис.4.8в), а все директивы условной макрогенерации исключены. Таким образом, предложения IF периода макрогенерации дают программисту удобные средства записи различных вариантов своей программы. Эти средства существенно отличны от предложений, подобных COMPR (или предложений IF в языках высокого уровня), которые осуществляют свои проверки во время выполнения программы. То же относится и к присваиванию значений переменным периода макрогенерации и другим директивам условной макрогенерации, которые мы еще обсудим.

Условная конструкция периода макрогенерации IF-ELSE-ENDIF является механизмом, позволяющим однократно перенести или пропустить некоторые строки из тела макроопределения в результирующую программу. На рис.4.9а приведен пример условных предложений периода макрогенерации другого типа. На нем изображено модифицированное определение макроса RDBUFF, назначение и функции которого те же, что и раньше. Но с помощью этого макроса программист может определить целый список признаков конца записи. Например, в предложении макроинициализации на рис.4.9б параметру EOR соответствует список (00, 03, 04). Любой из этих кодов является признаком конца записи. Для упрощения макроопределения параметр &MAXLTH удален; максимальная длина записи всегда полагается равной 4096.

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

Бек. Введение в системное программирование. 1988 8819810
Рис.4.9. Использование предложений условного перехода периода макрогенерации.

Примером использования конструкции WHILE-ENDW являются строки 63-73 на рис.4.9а. Переменной периода макрогенерации &EORCT предварительно (строка 27) было присвоено значение %NITEMS(&EOR). %NITEMS - это микропроцессорная функция, значением которой является количество элементов в списке, задаваемом в качестве аргумента этой функции. Например, если значение аргумента, соответствующее параметру EOR, есть (00, 03, 04), то значение %NITEMS(&EOR) будет равно 3.

Переменная периода макрогенерации &CTR используется для подсчета того, сколько раз сгенерированы (перенесены из тела макроопределения в результирующую программу) строки, следующие за предложением WHILE. Начальное значение &CTR полагается равным 1 (строка 63) и далее увеличивается на 1 при каждом проходе по циклу (строка 71). Само предложение WHILE говорит о необходимости повтора цикла периода макрогенерации до тех пор, пока значение переменной &CTR меньше или равно значению переменной &EORCT. Это означает, что строки 65-70 будут сгенерированы для каждого элемента списка. Переменная &CTR используется в качестве индекса для выборки соответствующего элемента списка. На первой итерации выражение &EOR[&CTR] в строке 65 будет иметь значение 00, на второй итерации - значение 03 и т.д.

На рис.4.9б изображено расширение предложения макроинициализации с использованием макроопределения на рис.4.9а. Вы должны тщательно проанализировать этот пример для того, чтобы убедиться, что вы правильно понимаете смысл предложения WHILE.

Реализация цикла WHILE периода макрогенерации также достаточно проста. Когда макропроцессор встречает предложение WHILE, он вычисляет соответствующее булевское выражение. Если оно ложно, то макропроцессор пропускает строки в таблице DEFTAB до предложения ENDW и продолжает далее работать, как обычно, в режиме безусловной макрогенерации. Если же это выражение истинно, то макропроцессор продолжает обрабатывать строки таблицы DEFTAB обычным образом до предложения ENDW. По предложению ENDW макропроцессор возвращается к предложению WHILE, перевычисляет соответствующее булевское выражение и действует далее в соответствии с этим новым вычисленным значением так, как уже описано.

Предложенный метод реализации не допускает вложенных конструкций WHILE. Вы можете подумать над тем, каким алгоритмом могут поддерживаться такие вложенные конструкции (см. упр.4.2.12).

4.2.4. КЛЮЧЕВЫЕ МАКРОПАРАМЕТРЫ

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

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

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

GENER ,,DIRECT,,,,,,3.

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

GENER TYPE=DIRECT,CHANNEL=3

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

На рис.4.10а изображен вариант макроса RDBUFF с использованием ключевых параметров. За исключением способа записи, все параметры те же, что и на рис.4.8а. В макропрототипе после каждого имени параметра следует знак равенства, являющийся признаком ключевого параметра. После знака равенства идет значение соответствующего параметра, принимаемое по умолчанию. Считается, что параметр принимает это значение, если его имя не встретилось в списке аргументов предложения макроинициализации. В нашем примере значение параметра &INDEV принимается равным F1, а для параметра &BUFADR значение по умолчанию не определено.

Бек. Введение в системное программирование. 1988 8820110
Рис.4.10. Использование ключевых параметров.

Определение значений некоторых параметров по умолчанию во многих случаях упрощает макроопределение. Например, оба макроопределения на рис.4.10а и рис.4.8а предусматривают установку максимальной длины записи равной 4096, если пользователь не задал другого значения. Если задано значение по умолчанию, как на рис.4.10а, то эти действия выполняются автоматически. Для выполнения этих же действий в макроопределении на рис.4.8а понадобилась конструкция IF-ELSE-ENDIF.

Другие фрагменты рис.4.10 содержат примеры макрорасширений предложения макроинициализации с ключевыми параметрами. На рис.4.10б все параметры имеют значения, заданные по умолчанию. На рис. 4.10в формальному ключевому параметру &INDEV соответствует значение F3, а значением параметра &EOR задана пустая строка. Значения, заданные для этих параметров по умолчанию, игнорируются. Обратите внимание, что аргументы в предложении макроинициализации могут быть расположены в любом порядке. Имеет смысл внимательно проследить на этих примерах использование заданных по умолчанию параметров.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Чт Фев 11, 2021 10:33 am

4.3. ВАРИАНТЫ ПОСТРОЕНИЯ МАКРОПРОЦЕССОРОВ

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

Хотя подавляющее большинство макросредств используется в связи с программированием на языке ассемблера, имеются также и другие приложения. В разд.4.3.2 обсуждается макропроцессор общего назначения, не связанный ни с каким конкретным языком программирования. В разд.4.4 приведен пример подобного макропроцессора. В разд.4.3.3 обсуждается другая сторона того же вопроса: интеграция макропроцессора с конкретным ассемблером или компилятором. Мы обсудим возможности кооперации между макропроцессором и транслятором и укажем на потенциальные преимущества и проблемы, которые возникают при такой интеграции.

4.3.1. РЕКУРСИВНАЯ МАКРОГЕНЕРАЦИЯ

На рис. 4.3 был представлен пример определения одной макроинструкции внутри другой. Мы, однако, не встречались с тем, чтобы внутри тела макроопределения были предложения макроинициализации. На рис.4.11 представлен пример подобного использования макросов. Макроопределение RDBUFF на рис.4.11а в основном то же, что и на рис.4.1. Для наглядности изменен только порядок параметров. В этом примере мы предполагаем, что макроопределение RDCHAR уже описано. Его функцией является чтение одного символа с заданного устройства в регистр А с учетом необходимых проверок готовности устройства. Соответствующее макроопределение изображено на рис.4.11б. Использование макроса, подобного RDCHAR, весьма удобно при определении макроса RDBUFF. Оно позволяет программисту при написании макроса RDBUFF не заботиться о деталях управления и доступа к устройству. (Макрос RDCHAR мог быть написан в другое время или даже другим программистом). Использование такого макроса даст еще большие преимущества на более сложной машине, на которой чтение одного символа осуществляется более длинной и сложной, чем в нашем случае, программой. К сожалению, изложенный выше алгоритм работы макропроцессора не позволяет обрабатывать инициализации макросов внутри других макросов. Предположим, например, что алгоритм на рис.4.5 обрабатывает предложение макроинициализации на рис.4.11в. После того как предложение макроинициализации будет распознано, будет вызвана процедура EXPAND. Аргументы из предложения макроинициализации будут помещены в таблицу ARGTAB, как это изображено на рис.4.11г. Булевской переменной EXPANDING будет присвоено значение TRUE, и начнется процесс макрорасширения. Он будет протекать правильно до строки 50, содержащей инициализацию макроса RDCHAR. В этом месте процедура PROCESSLINE обратится к процедуре EXPAND еще раз. Таблица ARGTAB будет преобразована к виду, представленному на рис.4.11д. Расширение макроса RDCHAR также осуществится правильно, однако после этого возникнут трудности. По концу макроопределения RDCHAR переменной EXPANDING будет присвоено значение FALSE. Таким образом, макропроцессор "забудет", что он находился в середине процесса макрорасширения, когда встретил предложение инициализации макроса RDCHAR. Кроме того, аргументы первой макроинициализации (RDBUFF) оказались потерянными, поскольку их значения в таблице ARGTAB затерлись новыми аргументами макроса RDCHAR.

Бек. Введение в системное программирование. 1988 8820310
Рис.4.11. Пример вложенных макроинициализаций.

Основной причиной этих трудностей является рекурсивный вызов процедуры EXPAND. Сначала она будет вызвана при обработке предложения макроинициализации макроса RDBUFF. Далее она вызывает процедуру PROCESSLINE (строка 50), которая делает еще одно обращение к EXPAND до возврата из первого вызова этой процедуры. Те же трудности возникнут и с процедурой PROCESSLINE, поскольку она также вызывается рекурсивно. Например, возникает вопрос, куда передавать управление после конца работы процедуры PROCESSLINE: в основной цикл работы макропроцессора или же в цикл внутри процедуры EXPAND.

Эти трудности легко разрешимы, если макропроцессор пишется на языке программирования, допускающем рекурсивные вызовы (таком как Паскаль или ПЛ/1). Сам компилятор позаботится о том, чтобы значения всех переменных, определенных внутри процедуры, были сохранены при рекурсивном обращении. Компилятор позаботится также и о других деталях обработки рекурсивных вызовов, включая определение адреса возврата из процедуры. (В гл.5 мы детально обсудим, как компилятор обрабатывает подобные рекурсивные вызовы). Если же допускающий рекурсию язык программирования недоступен, то программист обязан сам позаботиться о таких вещах, как адрес возврата и значения локальных переменных. В этом случае PROCESSLINE и EXPAND, может быть, вообще не будут оформлены как процедуры. Вместо этого та же логика работы может быть реализована с помощью операторов цикла с хранением значений переменных в стеке. Используемые здесь методы реализации описаны в гл.5 при обсуждении рекурсии. Пример подобной реализации содержится в работе Донован [1972].

4.3.2. МАКРОПРОЦЕССОРЫ ОБЩЕГО НАЗНАЧЕНИЯ

Наиболее часто макропроцессоры используются при программировании на языке ассемблера. Часто такие макропроцессоры встроены в ассемблер. Специализированные макропроцессоры были созданы также для некоторых языков программирования высокого уровня. (См., например, Керниган и Плаугер [1976].) Эти специализированные макропроцессоры во многом аналогичны с точки зрения выполняемых функций и используемого подхода. Их различия связаны в основном с особенностями конкретного языка программирования. В этом разделе мы обсудим макропроцессоры общего назначения. Они не зависят ни от какого конкретного языка программирования и могут быть использованы с целым рядом различных языков. Преимущества такого общего подхода к построению макропроцессоров очевидны. Программист не должен изучать специализированные макросредства для каждого компилятора или языка ассемблера. В результате экономится много времени и средств при обучении. Стоимость разработки макропроцессора общего назначения несколько больше по сравнению со специализированными макропроцессорами. Однако эти затраты не будут повторяться для каждого языка. Результатом будет существенное сокращение общих затрат на разработку и сопровождение математического обеспечения. На протяжении ряда лет экономия на сопровождении может превысить первоначальные затраты на разработку математического обеспечения.

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

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

Другие различия между языками программирования связаны со средствами группирования отдельных элементов языка, выражений или предложений. Большинство языков использует для этого скобки. В некоторых случаях макропроцессору общего назначения будет необходимо знать средства подобного группирования при сканировании исходных предложений. Однако некоторые языки используют другие символы вместо круглых скобок (например, символы [ и ]). Некоторые языки используют для этих целей ключевые слова begin и end.

Более общая проблема состоит в структуре лексем языка программирования, например идентификаторов, констант, операторов, ключевых слов. Языки существенно различаются по ограничениям, которые они накладывают на длину идентификаторов и правила написания констант. В некоторых случаях правила построения подобных лексем различны в разных частях программы (например, в предложении FORMAT языка Фортран или в предложении DATA DIVISION в языке Кобол). В некоторых языках имеются операторы, записываемые несколькими символами, такие как ** в языке Фортран и := в Паскале. Если макропроцессор будет воспринимать их как два независимых символа, а не как один оператор, могут встретиться определенные трудности. Даже формат записи предложений исходной программы во входном файле может привести к определенным трудностям. Макропроцессор должен учитывать, являются ли пробелы разделителями или должны полностью игнорироваться, способы записи одного предложения в нескольких строках, специальные соглашения по форматированию предложений, подобные принятым в языках Фортран и Кобол.

Другая проблема, которая может возникнуть при разработке макропроцессора общего назначения, связана с синтаксисом предложений макроопределения и макроинициализации. Для большинства специализированных макропроцессоров предложения макроинициализации весьма похожи на другие предложения базового языка программирования. (Например, инициализация макроса RDBUFF на рис.4.1 имеет ту же форму, что и предложение на языке ассемблера УУМ). Эта схожесть форм имеет целью облегчить написание и чтение программ. Этого трудно достичь для макропроцессора общего назначения, который предназначен для работы с языками программирования, имеющими различный формат предложений.

В разд. 4.4.3 мы кратко опишем макропроцессор общего назначения, в котором решены многие из перечисленных выше вопросов. Обсуждение макропроцессоров общего назначения и макропроцессоров для языков программирования высокого уровня содержится в работах Коул [1981], Браун [1974] и Кэмпбел-Келли [1973].

4.3.3. МАКРОПРОЦЕССОРЫ, ВСТРОЕННЫЕ В ТРАНСЛЯТОРЫ

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

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

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

Хотя макропроцессор, реализующий принцип "строка за строкой", и может использовать те же подпрограммы, что и транслятор, по-прежнему его функции и функции транслятора остаются существенно различными. Основной формой связи между ними является передача предложений программы с выхода одного на вход другого. Однако можно себе представить еще более тесное взаимодействие между макропроцессором и ассемблером. Речь идет о макропроцессоре, встроенном в транслятор.
Встроенный макропроцессор потенциально может использовать любую информацию об исходной программе, имеющуюся у транслятора. Объем этой информации существенно различается в разных системах. При относительно простом варианте взаимодействия макропроцессор использует такие операции транслятора, как сканирование, обработка констант и т.д. Эти операции в любом случае должны осуществляться компилятором или ассемблером; макропроцессор просто использует их результаты, не вдаваясь в такие детали, как изображение одной операции языка несколькими символами, расположение одного предложения языка на нескольких строках, формат лексем и т.п. Это особенно важно в тех случаях, когда эти правила различаются в разных частях программы (например, внутри операторов FORMAT и символьных констант языка Фортран).

Сканирование лексем, о котором только что шла речь, концептуально очень просто. Однако многие существующие языки программирования имеют особенности, доставляющие определенные трудности и на этом этапе. Классическим примером является предложение Фортрана

DO 100 I = 1, 20

Это оператор цикла DO, где DO распознано как ключевое слово, 100 - метка предложения, I - имя переменной и т.д. Поскольку пробелы никак не учитываются в предложениях Фортрана (кроме символьных констант), похожее предложение

DO 100 I = 1

имеет совершенно другой смысл. Это оператор присваивания переменной DO100I значения 1. Таким образом, правильная интерпретация термов DO, 100, I не может быть осуществлена до тех пор, пока не проведен анализ всего предложения. Такая интерпретация будет очень важна, например, в случае, если макропроцессор должен заменить переменную с именем I. Компилятор с Фортрана обязан разбираться с подобными ситуациями. Однако для обычного макропроцессора (не встроенного в компилятор) это сделать очень трудно. Такой макропроцессор может иметь дело с предложениями исходной программы только как со строками символов, не имея возможности выделить их отдельные элементы.

При более тесной связи с транслятором встроенный макропроцессор может обрабатывать макроинструкции, смысл которых зависит от того контекста, в котором они оказались. Например, с помощью такого макроса можно задать замену имен только переменных или констант определенного типа либо только переменных, являющихся переменными цикла в предложениях DO. Процесс макрогенерации может также зависеть от множества характеристик аргументов предложения макроинициализации. (Примером может служить описание макропроцессора System/370 в разд.4.5.1).

Макропроцессоры, тесно связанные с транслятором, имеют, конечно, и свои недостатки. Они должны быть спроектированы специально для работы с конкретной реализацией некоторого ассемблера или компилятора (а не просто для работы с языком программирования). Стоимость разработки такого макропроцессора должна быть прибавлена к стоимости разработки транслятора, что приводит к более дорогим компонентам математического обеспечения. Кроме того, ассемблер или компилятор становится больше и сложнее, чем это было бы в случае использования макропрепроцессора. Размер транслятора может представлять собой проблему, если транслятор предназначен для работы на ЭВМ с ограниченной оперативной памятью. В любом случае увеличиваются накладные расходы на трансляцию. (Некоторые ассемблеры со встроенными макропроцессорами требуют больше времени на обработку одной строки исходной программы, чем некоторые компиляторы на той же самой ЭВМ). Решение вопроса о типе макропроцессора, который, должен использоваться, опирается на данные о предполагаемой частоте и сложности макрогенерации и другие характеристики операционного окружения.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Чт Фев 11, 2021 10:38 am

4.4. ПРИМЕРЫ РЕАЛИЗАЦИИ

В этом разделе мы кратко опишем три реальных макропроцессора. Как и раньше, не будем пытаться охватить все характеристики каждой системы, а сосредоточимся на наиболее интересных особенностях. Первые два примера посвящены макропроцессорам, тесно связанным с ассемблером (для System/370 и ЭВМ серии VAX). Третий пример - макропроцессор общего назначения, предназначенный для использования в качестве препроцессора с самыми различными языками программирования.

4.4.1. МАКРОПРОЦЕССОР SYSTEM/370

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

Существенное отличие макропроцессора System/370 от рассмотренного нами макропроцессора для УУМ состоит в структуре предложений условной макрогенерации. В макроязыке для System/370 они называются предложениями условного ассемблирования. Хотя эти предложения и предназначены в основном для макрогенерации, они могут появиться также и вне макроопределений. Язык ассемблера System/370, дополненный операторами условного ассемблирования, допускает использование переменных периода ассемблирования, которые аналогичны обсуждавшимся выше переменным периода макрогенерации. Этим переменным могут быть присвоены арифметические, двоичные или символьные значения. Этим трем типам значений соответствуют три типа таких переменных. Переменные периода ассемблирования могут быть как локальными, так и глобальными. На локальные переменные можно ссылаться только внутри того макроопределения или такой подпрограммы на ассемблере, которые содержат их определения. Если локальные переменные, имеющие одно имя, будут определены в нескольких макроопределениях, то транслятор будет рассматривать их как различные переменные. Глобальные переменные могут использоваться в любом месте программы. Так, например, глобальной переменной может быть присвоено значение в момент обработки одного макроса, а использоваться это значение может в другом макросе.

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

На рис.4.12 изображены примеры макроопределения и текста макрорасширения с использованием макроязыка System/370. Это макроопределение предназначено для генерации кода, осуществляющего сложение двух элементов данных и запоминание результата. В зависимости от типа аргументов - целые или с плавающей точкой - порождается различный код. Если аргументы имеют какой-либо другой или не совпадающий между собой тип, то генерируется сообщение об ошибке. Форма макроопределения на рис.4.12а в основном та же, что и в наших предыдущих примерах. Макроопределение начинается предложением MACRO и заканчивается предложением MEND. Макропрототип расположен сразу же за предложением MACRO (строка 2). Все макропараметры и переменные периода ассемблирования начинаются символом &. В этом примере используются только позиционные параметры.

Бек. Введение в системное программирование. 1988 8821210
Рис.4.12. Примеры макроопределения и макрорасширений для System/370. [В строке 15 рис.4.12 имя метки должно быть MIXTYP.- Прим. перев.].

Предложение LCLC в строке 3 определяет переменную &TIPE строкового типа. Эта переменная определена как локальная в том макроопределении, в котором она описана. По умолчанию ей присваивается начальное значение - пустая строка. Предложение в строке 4 (AIF) является предложением условного перехода периода ассемблирования. Ассемблер вычисляет булевское выражение в скобках и переходит на обработку другого места исходной программы, если это выражение истинно. В противном случае ассемблер продолжает обрабатывать следующую строку исходной программы. В нашем примере адрес перехода задается с помощью специальной метки MIXTYP, которая описана в строке 15. Аргументами булевского выражения в предложении AIF являются типы параметров макроопределения. Тип параметра &OP1 (обозначаемый T'&OPl) есть символ F, если соответствующий параметр является целой переменной длиной в одно слово, и символ E, если он является числом с плавающей точкой. Таким образом, предложение AIF в строке 4 определяет переход к строке 15, если типы двух параметров различны. Если же их типы совпадают, то ассемблер продолжает работу со строки 5. Другое предложение AIF имеет аналогичную структуру.

Предложение AGO в строке 7 является оператором безусловного перехода периода ассемблирования, предложение ANOP (строки 8 и 10) - пустым оператором. Эти предложения используются для описания меток FLOAT и INTGR. В строках 14-17 содержатся макродирективы двух новых типов. Предложение MNOTE вызывает печать сообщения об ошибке в листинге ассемблера. Реакция ассемблера на это сообщение будет такой же, как реакция на собственную ошибку. Предложение MEXIT предписывает макропроцессору закончить процесс порождения текста макрорасширения текущего предложения макроинициализации, несмотря на то что предложение MEND еще не встретилось.

На рис.4.12б-г изображены предложения макроинициализации и соответствующие им макрорасширения. Предполагается, что I, J и К являются целыми переменными длиной в одно слово, а X, Y, Z - переменными с плавающей точкой. Чтобы убедиться, что вы правильно понимаете работу предложений AIF и AGO, вам необходимо проследить по шагам весь процесс макрогенерации, используя макроопределение на рис.4.12а.

В рассмотренном примере предложения AIF и AGO обеспечивают действия, эквивалентные конструкции IF-ELSE- ENDIF. Поскольку цикл WHILE также может быть запрограммирован с использованием предложений AIF и AGO, эти средства дают широкие возможности условного ассемблирования. Однако предложения AIF и AGO труднее реализовывать (и, вообще говоря, менее удобно использовать), чем предложения IF и WHILE, используемые для УУМ. Предложения условной макрогенерации для УУМ аналогичны управляющим операторам структурированного языка, подобного Паскалю. Предложения условного ассемблирования в System/370 больше похожи на средства неструктурированного языка, такого как Фортран IV.

В примере на рис.4.12 предложение AIF использовалось для проверки типа макроаргументов. Имеется множество других характеристик переменных и аргументов, которые могут использоваться сходным образом. Эти характеристики называются атрибутами соответствующих переменных и аргументов. В предложениях условного ассемблирования допустимо ссылаться на атрибуты объектов, определения которых еще не встречались в исходной программе. В результате ассемблер System/370 должен делать предварительный просмотр всей исходной программы, запоминая атрибуты всех объектов для их возможного использования в предложениях условного ассемблирования. Этот предварительный проход осуществляется до обработки каких бы то ни было макроинструкций, поэтому недопустимо ссылаться на атрибуты объектов, которые будут определены лишь в процессе макрогенерации.

Макропроцессор System/370 позволяет использовать ряд системных переменных. Можно считать, что они определены заранее и поэтому могут использоваться при макрогенерации. Примерами таких системных переменных могут служить текущая дата, время работы ассемблера, имя текущего управляющего раздела. Одна из системных переменных &SISNDX предназначена для генерации уникальных меток. Значением переменной &SISNDX является четырехразрядное целое число, первоначально равное 0001 и увеличивающееся на 1 при обработке каждого предложения макроинициализации. Используя эту переменную в качестве составной части метки, программист может избежать дважды определенных меток. Например, L&SISNDX может принять значения L0001, L0002 и т. д. при последовательной обработке предложений макроинициализации. Подробная информация о макропроцессоре System/370 содержится в IBM [1979] и IBM [1974].

4.4.2. МАКРОПРОЦЕССОР СИСТЕМЫ VAX

Макропроцессор системы VAX также сильно связан с ассемблером этой системы. Настолько сильно, что сам язык ассемблера назван VAX-11 и MACRO. Принципы записи макроопределений и предложений макроинициализаций те же, что и для УУМ, но есть и интересные отличия.

Имена параметров макроинструкций не обязаны начинаться с & или какого-либо другого специального символа. В результате процесс сканирования для обнаружения параметров усложняется. Кроме того, чаще должен использоваться оператор конкатенации. Рассмотрим в качестве примера макроопределение на рис.4.13а. В предложении

3 TSTL R'NUM

операндом является символ R, конкатенированный со значением параметра NUM. Для обозначения операции конкатенации в макроязыке системы VAX используется апостроф '. Если бы этот операнд был записан просто как RNUM, то последовательность символов NUM не была бы распознана в качестве макропараметра. Таким образом, оператор конкатенации тут совершенно необходим. Сравните эту ситуацию с примером на рис.4.12. В строке 13 этого примера не требуется никакого оператора конкатенации при записи терма ST&TYPE, поскольку амперсанд является признаком того, что &TYPE является параметром. Вообще говоря, макропроцессор мог бы распознать последовательность символов NUM на рис.4.12 как имя параметра без оператора конкатенации. Однако в этом случае макропроцессор должен был бы иметь средства, позволяющие запретить подстановку параметров (например, мы можем не захотеть, чтобы NUMBER было превращено в 5BER). Если же макропараметр не сконкатенирован ни с какими другими символами, то апостроф, конечно же, не нужен.

Бек. Введение в системное программирование. 1988 8821610
Рис.4.13. Примеры макроопределения и макрорасширений для ЭВМ серии VAX.

Макропроцессор системы VAX также предоставляет средства генерации уникальных локальных меток при макрогенерации. Программист описывает локальную метку, путем включения ее в список параметров макроопределения, добавив предварительно перед именем метки символ ?. Задав соответствующий аргумент в предложении макроинициализации, можно дать этой метке произвольное значение. Если же оно не определено, то ассемблер сгенерирует новую локальную метку. Метки, генерируемые ассемблером, находятся в диапазоне 30000$-65535$

Этот процесс проиллюстрирован макроопределением на рис.4.13а, предложениями макроинициализации и соответствующими макрорасширениями на рис.4.13б-в. В первом макрорасширении метка L1 заменена на 30000$; во втором - на 30001$. Программист может также определить локальные метки вне тела макроопределения (в предложении макроинициализации). Однако, чтобы не было пересечений с метками, генерируемыми макросредствами, документация ассемблера требует, чтобы метки пользователя не попадали в интервал 30000$-65535$. Вы можете сами проследить процесс получения макрорасширения на рис.4.13, обращая внимание на использование операторов конкатенации и локальных меток.

Дальнейшая информация о макропроцессоре системы VAX содержится в DEC [1972] и DEC [1979].

4.4.3. МАКРОПРОЦЕССОР ОБЩЕГО НАЗНАЧЕНИЯ РМ

В этом разделе мы дадим краткое описание макропроцессора общего назначения, имеющего имя РМ (Pattern Matching). Более подробное описание этой системы имеется в Сасс [1979].

Макроопределения и предложения макроинициализации макропроцессора РМ существенно отличаются от рассмотренных ранее. Средства записи макрошаблонов (макропрототипов), соответствующие по назначению заголовкам макроопределений, имеют множество различных вариантов. Некоторые части шаблона могут быть вообще опущены; могут содержать набор альтернативных конструкций; могут повторяться столько раз, сколько необходимо. Процесс макрогенерации инициализируется при совпадении макрошаблона с некоторым фрагментом входного текста. Предложение макроинициализации (и даже его отдельные аргументы) может быть записано в нескольких строках исходного файла. Язык записи тел макроопределений похож на Алгол.

Макропроцессор допускает определение пользователем локальных и глобальных меток периода макрогенерации. Имеются также системные переменные периода макрогенерации, аналогичные тем, с которыми мы встречались при анализе макропроцессора System/370. Одна из таких системных переменных является счетчиком количества обработанных предложений макроинициализации. Значение этой переменной может быть использовано для генерации уникальных меток. Допустимы также предложения условной макрогенерации, которые функционально (но не по способу записи) близки к условным предложениям макроязыка УУМ.

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

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

На рис.4.14 изображен пример макроопределения и предложения макроинициализации для макропроцессора РМ. Макроопределение на рис.4.14а разработано для использования с языком Фортран. Предложение макроинициализации по форме похоже на алгольное предложение for; выходной текст имеет формат Фортрана. Строка 1 определяет шаблон предложения макроинициализации: ключевое слово for, за которым следует параметр id, за которым следует ключевое слово from и т.д. Предложение, заключенное в скобки, определяет альтернативные варианты построения предложения макроинициализации. Выражение в скобках может либо быть пустым (обозначено символом /), либо содержать конструкцию by. Таким образом, в предложении макроинициализации конструкция by может либо содержаться, либо быть опущена. Спецификация noneg, следующая за параметром body, определяет, что пробелы и символы "конец строки" являются значимыми (т.е. не должны игнорироваться) при обработке значений макроаргументов.

Бек. Введение в системное программирование. 1988 8821810
Рис.4.14. Примеры макроопределения и макрорасширений для макропроцессора РМ.

Тело макроопределения записано в алгольном стиле в строках 2-11. Текст, который должен появиться в макрорасширении, заключен в угловые скобки. В строках 3-6 содержатся четыре строки выходного текста. Внутри выходного текста символ % используется для выделения параметров и имен переменных периода макрогенерации. Так, например, строка 3 говорит о том, что в выходной файл должно быть переписано id, за которым следует знак равенства, за которым следует значение параметра f. Далее строка кончается. Конец строки входного текста порождает конец строки и в выходном тексте. Предполагается, что переменной периода макрогенерации snum ранее уже присвоено начальное значение 9000. Эта переменная используется для генерации меток предложений Фортрана. В результате обработки строки 7 в выходном тексте появится либо значение параметра b, либо 1 в зависимости от того, был ли задан аргумент, соответствующий параметру b. Его значение окажется на той же строке, которая начала образовываться при обработке строки 6 макроопределения. Символы %/, которыми начинается строка 8, означают конец строки. Таким образом, предложение GOTO будет расположено в отдельной строке. Оператор присваивания в строке 10 увеличивает значение переменной snum периода макрогенерации таким образом, что в следующем макрорасширении метки оператора Фортрана не будут совпадать с ранее сгенерированными.

На рис.4.14б изображено предложение, которое будет обработано как предложение макроинициализации только что описанного макроопределения. На рис.4.14в приведен сгенерированный текст. (Детали,определяющие формат выходных фортрановских строк, опущены). Предполагается, что было определено соответствующее макроопределение, заменяющее символы := на символ = в операторе присваивания S:=S+A(I). Подробно проанализируйте этот пример, чтобы лучше понять процессы макроинициализации и макрогенерации, реализованные в макропроцессоре РМ.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Бек. Введение в системное программирование. 1988 Empty Re: Бек. Введение в системное программирование. 1988

Сообщение автор Gudleifr Чт Фев 11, 2021 10:42 am

УПРАЖНЕНИЯ

РАЗДЕЛ 4.1
1. Примените приведенный на рис.4.5 алгоритм для обработки исходной программы на рис.4.1. Результат должен совпасть с изображенным на рис.4.2.

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

3. Предположим, мы хотим видеть макроопределения внутри ассемблерного листинга. Каким образом макропроцессор и ассемблер могут это реализовать?

4. В большинстве случаев фрагменты комментариев не должны заменяться на значения макроаргументов, даже если они и совпадают с именами макропараметров. Каким образом можно предотвратить замену параметров внутри комментариев?

5. На основании чего программист должен принять решение о реализации некоторой конкретной логической функции с помощью подпрограммы или макроопределения?

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

7. Модифицируйте приведенный на рис.4.5 алгоритм таким образом, чтобы макропроцессор брал не описанное программистом макроопределение из библиотеки.

8. Предложите некоторый способ работы с таблицами DEFTAB и NAMTAB.

9. Предположим, что вхождения макропараметров в таблице DEFTAB не заменены соответствующими позиционными обозначениями ?n. Какие изменения это потребует в алгоритме работы макропроцессора на рис.4.5?

РАЗДЕЛ 4.2
1. Макроопределение на рис.4.1 содержит несколько предложений, в которых макропараметры сконкатенированы с другими символами (например, строки 50 и 75). Почему в этих предложениях нет необходимости использовать оператор конкатенации?

2. Модифицируйте приведенный на рис.4.5 алгоритм так, чтобы он обрабатывал операторы конкатенации.

3. Модифицируйте приведенный на рис.4.5 алгоритм так, чтобы предоставить пользователю средства генерации уникальных меток при макрогенерации.

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

5. В чем заключается наиболее существенное различие между следующими фрагментами:

Бек. Введение в системное программирование. 1988 8822010

а) LDA ALPHA
COMP #0
JEQ SKIP
LDA #3
STA BETA
SKIP ...
б) IF (&ALPHA NE 0)
&BETA SET 3
ENDIF

6. Получите макрорасширения следующих двух предложений макроинициализации, используя макроопределения, приведенные на рис.4.8а
а) RDBUFF F1,BUFFER,LENGTH,00,1024
б) LOOP RDBUFF F2,BUFFER,LTH

7. Модифицируйте приведенный на рис.4.5 алгоритм так, чтобы допустить использование операторов присваивания и конструкций IF-ELSE- ENDIF периода макрогенерации. Можно считать, что вложенные конструкции IF отсутствуют.

8. Дополните свой ответ к упр.7 так, чтобы допустить наличие вложенных предложений IF.

9. В чем наиболее существенная разница между следующими двумя фрагментами:

Бек. Введение в системное программирование. 1988 8822110

a) LDT #8
CLEAR X
LOOP ...
...
TIXR T
JLT LOOP
б) &CTR SET 0
WHILE (&CTR LT 8 )
...
&CTR SET &CTR+1
ENDW

10. Используя макроопределения на рис.4.9а, получите текст макрорасширений для следующих предложений макроинициализации:
а) RDBUFF F1,BUFFER,LENGTH,(04,12)
б) LABEL RDBUFF F1,BUFFER,LENGTH,00
в) RDBUFF F1,BUFFER,LENGTH

Какое значение будет иметь функция %NITEMS(&EOR) в последних двух случаях?

11. Дополните свой ответ к упр.7 так, чтобы включить предложение WHILE. Можете не заботиться о вложенных конструкциях WHILE.

12. Дополните свой ответ к упр.11 так, чтобы допустить вложенные конструкции WHILE.

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

14. Модифицируйте приведенный на рис.4.5 алгоритм с тем, чтобы включить в него обработку ключевых параметров макроопределений.

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

16. Каким образом можно было бы задать значения по умолчанию для позиционных параметров? Какие изменения в алгоритме на рис.4.5 пришлось бы сделать для введения такой возможности?

17. Вспомним макроопределение RDBUFF на рис.4.8а. Каждое из нижеследующих предложений макроинициализации этого макроопределения содержит ошибку. Какие из этих ошибок будут выявлены макропроцессором, а какие - ассемблером?
а) RDBUFF F3,BUF,RECL,ZZ (неправильное значение для &EOF)
б) RDBUFF F3,BUF,RECL,04,2048,01 (слишком много аргументов)
в) RDBUFF F3,,RECL,04 (не определено значение для &BUFADR)
г) RDBUFF F3,RECL,BUF (неправильный порядок аргументов)

РАЗДЕЛ 4.3
1. Предположим, что макропроцессор с логикой работы, аналогичной изображенной на рис.4.5, должен обеспечивать рекурсивную макрогенерацию. При обсуждении было выявлено, что значения переменной EXPANDING и ARGTAB должны запоминаться при рекурсивных вызовах процедуры EXPAND. Какие другие значения также необходимо запоминать для разных способов реализации алгоритма?

2. Каким образом рекурсивный макропроцессор может быть реализован на языке макропроцессора?

3. Можно ли в нерекурсивном макропроцессоре допустить появление предложений макроинициализаций внутри макроопределений? Каковы будут преимущества и недостатки такого подхода?

4. Выберите два знакомых вам языка программирования высокого уровня. Какие особенности этих языков будут существенны при реализации макропроцессора для них?

5. Выберите какой-либо знакомый вам язык высокого уровня и язык ассемблера. Какие особенности этих двух языков будут существенны для реализации макропроцессора для них?

6. Опишите алгоритм взаимодействия макропроцессора, реализующего режим "строка в строку", с ассемблером.

7. Перечислите вспомогательные функции и процедуры, которые могли бы быть общими для ассемблера и встроенного макропроцессора.
Gudleifr
Gudleifr
Admin

Сообщения : 3246
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Страница 1 из 2 1, 2  Следующий

Вернуться к началу

- Похожие темы

 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения