Текст
                    Л. Бек
ВВЕДЕНИЕ
В СИСТЕМНОЕ
ПРОГРАММИРОВАНИЕ
Издательство
'Мир

ВВЕДЕНИЕ В СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ
System Software An introduction to systems programming Leland L. Beck San Diego State University Addison-Wesley Publishing Company Reading, Massachusetts • Menlo Park, California < Don Mills, Ontario • Wokingham, England • Amsterdam • Sydney • Singapore • Tokyo • Mexico City • Bogota • Santiago • San Juan
Л.Бек ВВЕДЕНИЕ В СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ Перевод с английского Н. А. Богомолова, В. М. Вязовского и С. Е. Морковина под редакцией Л. Н. Королева МОСКВА «МИР» 1988
ББК 32.973 Б 42 УДК 681.142 Бек Л. Б 42 Введение в системное программирование: Пер. с англ.—• М.: Мир, 1988. — 448 с., ил. ISBN 5-03-000011-9 Монография учебного характера, написанная американским специалистом* В ней изложены все основные компоненты системного программного обеспече- ния. Особое внимание уделено их взаимосвязи с архитектурой вычислительных комплексов. Конкретные реализации компонентов обсуждаются на примере трех современных вычислительных систем: IBM/370, VAX, CYBER. Для системных программистов, аспирантов, студентов вузов. _ 1702070000-268 .. ,, Б " Ml (01)-88 44“88’ Ч- 1 ББК 32‘ 973 Редакция литературы по математическим наукам Учебное издание Леланд Л. Бек ВВЕДЕНИЕ В СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ Заведующий редакцией д-р физ.-мат. наук профессор | Б. В. Шабат | Зам. зав. редакцией А. С. Попов Научн. редактор М. В. Хатунцева Мл. редактор Т. Ю. Дехтярева Художник Н. М. Иванов Художественный редактор В. И. Шаповалов Технический редактор Л. П. Емельянова Корректор М. А. Смирнов ИБ № 6315 Сдано в набор 25.08.87. Подписано к печати 18.05.88. Формат 60X90/16. Бумага книжно-журнальная. Печать высокая. Гарнитура литературная. Объем 14 бум. л. Усл. печ. л. 28. Усл. кр.-отт. 28. Уч.-изд. л. 32,75. Изд. № 1/5213. Тираж 50000 экз. Зак. 792. Цена 2р.60. к ИЗДАТЕЛЬСТВО «МИР» 129820, ГСП, Москва, И-110, 1-й Рижский пер., 2 Ленинградская типография № 2 головное предприятие ордена Трудового Красного Зна- мени Ленинградского объединения «Техническая книга» нм. Евгении Соколовой Союэ- полиграфпрома при Государственном комитете СССР по делам издательств, полиграфии и книжной торговли. 198025, г. Ленинград, Л-52, Измайловский проспект, 29. ISBN 5-03-000011-9 (русск.) ISBN 0-201-10950-6 (англ.) © 1985 by Addison-Wesley Publishing Company. Inc. © перевод на русский язык, «Мир», 1988
От редактора перевода Предлагаемая вниманию читателей книга может рассматри- ваться как учебное пособие по курсу «Системное программиро- вание». Ее несомненное достоинство — хорошо продуманная ме- тодика представления материала и разумный компромисс меж- ду детальностью изложения конкретных элементов системного программного обеспечения и общими сведениями об основных концепциях, принятых при создании этих элементов. Эта мето- дика автора заключается в следующем: сначала рассматрива- ются машинно-независимые характеристики того или иного ком- понента базового программного обеспечения, затем следует из- ложение того, как реализованы эти компоненты на конкретных ЭВМ. В качестве таких ЭВМ автор выбрал очень непохожие друг на друга: System/370, VAX и CYBER. Примеры систем- ных программ приводятся для двух выдуманных автором упро- щенных учебных машин, которые по своей структуре очень близки к современным микропроцессорам. Это дает возмож- ность читателю достаточно детально понять, как программиру- ются отдельные компоненты, одновременно не теряя из виду общей картины взаимосвязи компонентов системного программ- ного обеспечения; в то же время он получает информацию о том, как все это реализовано на настоящих машинах. В отли- чие от ряда других переводных изданий данная книга не. ориентирована на какой-то определенный класс машин и кон- кретный состав программного обеспечения, а дает более широ- кое представление о характеристиках и свойствах системных программ. Следует отметить умение автора просто и понятно расска- зать о принципах реализации таких сложных элементов компи- ляторов, как рекурсивные процедуры, окна видимости данных, машинно-независимая и машинно-зависимая оптимизации генерируемого объектного кода, о построении повторно входи- мых процедур. Из общего стиля изложения несколько выпадает гл. 7, в основном содержащая перечень требований, которым должны удовлетворять такие важные компоненты, как управление ба- зами данных, текстовые редакторы, системы интерактивной от- ладки. Автор иногда отступает от общепринятой терминологии и вводит свои термины. Например, им введен термин «целевой
6 От редактора перевода адрес». Этот термин вполне уместен: им обозначается некото- рым образом вычисленный код, который используется в каче- стве либо исполнительного адреса, либо косвенного адреса, либо непосредственного операнда. Автор в одном и том же смысле употребляет термины «архитектура» и «структура» ЭВМ и не делает различия' между терминами «компиляция» и «трансляция», однако это не приводит к недоразумениям. При переводе книги сохранена терминология автора и в тех слу- чаях, когда это необходимо по тексту, даны соответствующие примечания. Данная книга будет полезна многим читателям, и прежде всего преподавателям вузов, ведущих занятия по системному программированию, и разработчикам системных программ. Она будет полезна учителям средних школ, преподающих основы информатики и вычислительной техники и ведущим факульта- тивные занятия по данному предмету. Несомненно, она послу- жит хорошим пособием для студентов, специализирующихся в области системного программного обеспечения ЭВМ. Л. Н. Королев
Посвящается Кристоферу Лорену Предисловие Эта книга представляет собой введение в проектирование и реализацию компонентов системного программного обеспечения. Ее центральная тема —взаимосвязь между архитектурой ЭВМ и системными программами. Примерами системных компонен- тов, которые испытывают значительное влияние со стороны ма- шинной архитектуры, являются ассемблеры и операционные си- стемы. Чтобы особо подчеркнуть и продемонстрировать такое влияние, мы рассмотрим реально существующие компоненты системных программ для целого ряда машин. Однако программ- ное обеспечение различных вычислительных систем имеет и сходные черты (например, базовая структура и схема ассембле- ра по существу одни и те же для большинства ЭВМ). Эти ос- новные машинно-независимые аспекты системных программ четко определяются и отделяются от деталей, обусловленных спецификой конкретной ЭВМ. Основное назначение настоящей книги — служить учебным пособием для студентов, а также аспирантов по курсу «Систем- ное программное обеспечение» или «Системное программирова- ние». Ею также можно воспользоваться в качестве справочника или пособия для самостоятельного изучения этих дисциплин. Предполагается, что читатель знаком по крайней мере с одним из языков ассемблера и общими методами представления команд и данных в памяти ЭВМ (например, восьмеричной и шестнадцатеричной нотацией, представлением отрицательных чисел в обратном и дополнительном кодах). Эти темы хорошо рассмотрены в работах Пфлигера [1982] и Гира [1981]. Кроме того, предполагается знакомство читателя с методами исполь- зования базовых структур данных, в особенности связанных списков и хеш-таблиц. Материал по этим темам можно найти в любом хорошем учебнике по структурам данных, например у Трембли и Соренсона [1984], Стендиша [1980] или Кнута [[1973а, б]. В гл. 1 содержится краткое введение и описывается упро- щенная учебная машина (УУ/М), используемая для представле- ния основных концепций программного обеспечения. В ней так- же описываются реальные вычислительные системы (Sy- stem/370, VAX, CYBER), которые в дальнейшем используются в качестве примеров. Эти машины резко различаются по своей архитектуре, и поэтому они были выбраны для иллюстрации разнообразия программных и аппаратных систем. Глава 2 посвящена вопросам разработки ассемблеров. В разд. 2.1 в качестве учебного инструмента для представле-
в Предисловие ния базовых концепций ассемблирования программ использует- ся УУМ. Ввиду того что основные функции и характеристики ассемблера слабо зависят от конкретной машины, этот мате- риал может служить студенту отправной точкой при проекти- ровании ассемблера для новой или ранее не знакомой ему ЭВМ. В разд. 2.2 обсуждаются машинно-зависимые аспекты расширения базовых функций ассемблера, описанных в разд. 2.1. Такое обсуждение помогает подчеркнуть взаимосвязь между архитектурой ЭВМ и теми решениями, которые прини- мают при проектировании и реализации ассемблеров. В разд. 2.3 вводится ряд машинно-независимых средств ассемб- леров, а в разд. 2.4 обсуждаются некоторые важные альтерна- тивные варианты общей схемы ассемблирования. Наличие воз- можностей, описанных в разд. 2.3, так же как и выбор схемы ассемблирования, не диктуется машинными особенностями, а определяется разработчиком программного обеспечения. Здесь нет какого-либо единственно «правильного» способа реализации. Поэтому разработчик программного обеспечения должен отда- вать себе полный отчет в существовании других решений для того, чтобы сделать разумный выбор между ними. Наконец, в разд. 2.5 обсуждаются примеры существующих ассемблеров для реальных ЭВМ. Эти примеры иллюстрируют как машинно-за- висимые, так и машинно-независимые варианты решения тех или иных вопросов, что существенно дополняет материал, рас- смотренный в предыдущих разделах. Аналогичный общий подход используется при обсуждении загрузчиков (гл. 3), макропроцессоров (гл. 4), компиляторов (гл. 5) и операционных систем (гл. 6). Вначале описываются базовые характеристики рассматриваемого системного компо- нента. Затем следует обсуждение машинно-зависимых и машин- но-независимых возможностей, расширяющих базовые функции. Далее рассматриваются другие возможные подходы к проекти- рованию и даются примеры реальных системных программ. Глубина изложения материала в различных главах значи- тельно варьируется. Главы 2—4 содержат достаточно полное обсуждение ассемблеров, загрузчиков и макропроцессоров. Опи- сываемые здесь детали реализации (алгоритмы и структуры данных) должны в принципе позволить студенту самостоятельно написать работающий вариант системной программы на основе приведенных схем. (Я настоятельно рекомендую, чтобы курс, основанный на данной книге, предусматривал такой проект.) С другой стороны, темы, затрагиваемые в гл. 5 и 6, значи- тельно шире. Каждая из них сама по себе являлась предметом многих отдельных монографий. Очевидно, что невозможно сколько-нибудь полно обсудить их в рамках одной главы. Вме- сто этого ставилась задача дать читателю краткий (но отнюдь не поверхностный) обзор компиляторов и операционных систем.
Предисловие 9 Наиболее важные и фундаментальные концепции данных ком- понентов программного обеспечения иллюстрируются на приме- рах. Более сложные вопросы лишь отмечаются в тексте, а для читателя, желающего рассмотреть их более глубоко, даются ссылки на соответствующую литературу. Из-за недостатка мес- та большинство деталей реализации было опущено. Аналогич- ный подход использован в гл. 7, посвященной системам управ- ления базами данных, текстовым редакторам и интерактивным системам отладки. Упражнения, приведенные в конце гл. 2—6, являются важ- ной составной частью книги. Они предназначены для стимули- рования индивидуального мышления и дискуссий в аудитории. Некоторые из этих упражнений представляют собой открытые проблемы, не имеющие единственного «правильного» ответа. Многие упражнения требуют от читателя умения применить понятия, изложенные в основном тексте, к новым ситуациям. Успешное решение предлагаемых упражнений является гаран- тией того, что читатель глубоко усвоил рассмотренные принци- пы и в состоянии использовать их в практической работе. Я намеренно не включил в книгу ответы на упражнения, так как полагаю, что список ответов был бы скорее фактором, сдер- живающим творческое мышление, нежели стимулирующим его. Объем книги превышает обычный односеместровый курс, что позволяет преподавателю выбрать нужную степень глубины из- ложения различных тем в зависимости от потребностей кон- кретного учебного плана. Например, если предполагается, что впоследствии студенты будут слушать специальный курс по операционным системам, то преподаватель может опустить гл. 6, при этом материала оставшихся глав будет достаточно для типового односеместрового курса. .Другие преподаватели могут предпочесть рассмотрение всех основных глав, исключив некоторые разделы, в которых обсуждаются более сложные вопросы. Одним из моментов, заслуживающих комментария, является применение для целей обучения гипотетической ЭВМ (УУМ). Я использовал гипотетическую машину в первую очередь пото- му, что это позволяет избежать не связанных с существом дела сложностей и «причуд», встречающихся в реальных машинах. Кроме того, использование такой машины позволяет четко от- делить фундаментальные концепции тех или иных компонентов программного обеспечения от деталей реализации, связанных со спецификой ЭВМ. Если для целей обучения используется реальная машина, то у студента не всегда есть уверенность в том, что он правильно понимает, какие характеристики про- граммного обеспечения являются действительно фундаменталь- ными, а какие — просто следствием использования конкретной ЭВМ.
10 Предисловие Второе преимущество использования УУМ заключается в том, что практически все студенты будут иметь одинаковую начальную подготовку, т. е. не будет студента, который ока- жется в худшем положении из-за того, что он незнаком с кон- кретной ЭВМ и ее программным обеспечением, положенными в основу книги. Я считал, что это будет особенно важно в мо- ем курсе, который предназначен для студентов, имевших опыт работы с различными машинами. Само собой разумеется, что при изучении подобного курса студенты должны иметь возможность писать программы и про- пускать их на машине, которую они изучают. Интерпретатор УУМ может эксплуатироваться практически на любой ЭВМ, поддерживающей Паскаль. Это дает студентам возможность разрабатывать и пропускать системные программы так, как если бы они на самом деле имели доступ к УУМ. Наличие пе- реносимого интерпретатора означает, что нет необходимости в использовании какой-либо конкретной вычислительной систе- мы, так как программы для УУМ могут выполняться на любой ЭВМ, удобной для преподавателя и студентов. Наконец, следует заметить, что некоторые из рецензентов вначале скептически относились к УУМ, но изменили свое мне- ние, увидев, как она может быть использована в качестве учеб- ного инструмента. Многие отдали время и энергию для улучшения этой книги. Я хочу выразить особую признательность Саре Баас за то, что она с самого начала моей работы над книгой делилась со мной своим опытом автора. Сара Баас, Стив Брюел, Тим Будд, Род- жер Кинг, Томас Ньюджент, Чарльз Пфлигер, Поль Росс, Вер- нор Винг и Бернард Уейнберг отрецензировали значительные части рукописи. Их замечания и предложения были крайне по- лезны для меня при выискивании ошибок и решении других проблем, связанных с рукописью. За все ошибки, которые тем не менее остались в тексте, отвечаю я лично и буду весьма признателен всем, кто укажет мне на них. Я хотел бы поблагодарить Мэтта Белшина, Джеффа Блэк- мона, Пэта Коффеи, Глена Джерпсета, Сэнду Швиммер и Боба Свенсона за их помощь в поиске справочного материала по конкретным системам. Я также в долгу перед студентами, ко- торые использовали предварительный вариант книги в качестве учебника и сделали много ценных предложений. Наконец, я глубоко признателен моей жене, семье, друзьям, коллегам и студентам за ту атмосферу понимания и помощи, которую они создали мне в период, когда мое время и энер- гия были отданы работе над книгой. Без их терпения и дове- рия эта работа продвигалась бы гораздо труднее. Август 1984 г, Л. Л. Бя
Глава 1. Основные понятия В данной главе содержится различная информация, которая послужит нам основой при изучении последующих глав, В разд. 1.1 приводится обзор структуры книги и дается крат- кое введение в системное программное обеспечение. С разд. 1.2 начинается обсуждение взаимосвязи между системным про- граммным обеспечением и структурой ЭВМ, которое продол- жится в дальнейшем на протяжении всей книги. В разд. 1.3— 1.7 даются общие сведения об архитектуре некоторых ЭВМ, используемых далее в качестве примеров. Более детальное обсуждение большинства вопросов, касающихся архитектуры машин, можно найти в работах Танненбаум [19841, ПФлигео [1982] и Гир [1981]. Основная часть данной главы содержит лишь общие сведе- ния; многие детали опущены. Уровень детализации выбран та- ким, чтобы он обеспечивал лучшее понимание последующих глав. Не следует стремиться запоминать материал этой главы или углубляться в малозначительные вопросы. Вместо этого рекомендуется главу прочитать, а затем, при изучении после- дующих глав, использовать ее по мере необходимости в каче- стве справочника. Для читателей, желающих получить более подробную информацию по тем или иным вопросам, приводят- ся необходимые ссылки на литературу. 1.1. Введение Эта книга является введением в проектирование различных компонентов системного программного обеспечения. Мы также рассмотрим реализацию такого программного обеспечения для некоторых реально существующих машин. Одна из централь- ных проблем книги — взаимосвязь системного программного обеспечения и архитектуры ЭВМ. Очевидно, что структура це- левой машины оказывает влияние на выбор тех или иных ре- шений, применяемых при создании системных программ. Неко- торые аспекты такого влияния обсуждаются в разд. 1.2, другие будут рассмотрены в остальных главах книги. Основные темы, которые рассматриваются в данной книге, касаются ассемблеров, загрузчиков, макропроцессоров, компи- ляторов и операционых систем. Каждая из гл. 2—6 посвящена одной из них. Предполагается, что с точки зрения пользователя
12 Гл. 1. Основные понятия читатель знаком со всеми рассматриваемыми здесь системными компонентами. В первую очередь в данной книге затрагивают- ся вопросы проектирования и реализации системного обеспече- ния. В гл. 7 содержится обзор некоторых других важных си- стемных компонентов: систем управления базами данных, тек- стовых редакторов и систем интерактивной отладки. Глубина изложения материала неодинакова. Главы, посвя- щенные ассемблерам, загрузчикам и макропроцессорам, содер- жат достаточно детальную информацию, позволяющую подгото- вить читателя к тому, чтобы он мог самостоятельно написать данные компоненты программного обеспечения для реальных машин. С другой стороны, компиляторы и операционные систе- мы являются слишком обширными темами, каждая из которых сама по себе не раз была объектом многих отдельных моно- графий и курсов. Очевидно, что невозможно полностью рассмо- треть какую-либо из этих тем в одной главе сколько-нибудь разумного размера. Поэтому вместо этого предлагается введе- ние в наиболее важные понятия и вопросы, относящиеся к ком- пиляторам и операционным системам; особое внимание уделя- ется взаимосвязи программного обеспечения и структуры ЭВМ. Другие подтемы обсуждаются настолько, насколько позволяет место, и снабжаются ссылками на литературу, предназначенны- ми для читателей, желающих изучить их более глубоко. Наша цель — дать хороший общий обзор рассматриваемых тем, кото- рый мог бы послужить в дальнейшем базой при изучении сту- дентами специальных курсов по программированию. Аналогич- ный подход используется и при изложении материала гл. 7, 1.2. Системное программное обеспечение и структура ЭВМ Машинная зависимость является одной из характеристик, которая обычно отличает системное программное обеспечение от прикладного. Прикладная программа интересует нас глав- ным образом с точки зрения решения некоторой задачи. При этом ЭВМ используется как инструмент и основное внимание сосредоточено на предметной стороне дела, а не на вычисли- тельной системе. С другой стороны, системные программы пред- назначены скорее для обеспечения управления функционирова- нием собственно ЭВМ, чем для решения какой-либо конкрет- ной задачи. Вследствие этого они обычно тесно связаны со структурой машины, для которой созданы. Например, ассемб- леры при переводе мнемонических инструкций в машинный код непосредственно учитывают форматы команд, способы адреса- ции и другие аппаратные характеристики целевой машины.
1.2. Системное программное обеспечение 13 Компиляторы должны генерировать код на машинном языке, учитывая такие характеристики аппаратуры, как число и спо- соб использования регистров или имеющиеся в наличии коман- ды. Операционные системы непосредственно отвечают за управ- ление практически всеми ресурсами вычислительной системы, В дальнейшем мы увидим много других примеров машинной зависимости. С другой стороны, системное программное обеспечение имеет ряд аспектов, непосредственно не связанных с типом вычисли- тельной системы, которую они поддерживают. Так, общая схе- ма и алгоритмы ассемблера в основном не различаются для большинства ЭВМ. Некоторые из способов оптимизации объект- ного кода, используемые в компиляторах, не зависят от целе- вой машины (хотя существует также и машинно-зависимая оптимизация). Точно так же обычно не зависит от используем мой ЭВМ и процесс установления связей между отдельно ас- семблированными подпрограммами В последующих главах мы рассмотрим много других примеров аналогичных машинно- независимых характеристик. Ввиду того что большинство системных программ машинно- зависимые, мы должны в процессе обучения рассматривать ре- альные машины и реальные компоненты программного обеспе- чения. Однако большинство реальных ЭВМ обладает определен- ными нестандартными или даже уникальными особенностями. Поэтому порой бывает трудно отличить действительно основ- ные свойства программного обеспечения от свойств, зависящих исключительно от специфических особенностей конкретной ма- шины. Для того чтобы обойти эти трудности, мы для изуче- ния основных функций каждого из компонентов программного обеспечения будем использовать Упрощенную Учебную Машину (УУМ). УУМ представляет собой гипотетическую ЭВМ, при проектировании которой ставилась цель включить в нее аппа- ратные возможности, наиболее часто встречающиеся в реаль- ных ЭВМ, исключив в то же время не относящиеся к существу дела или редко встречающиеся сложности. Таким образом, ос- новные концепции каждого компонента системного программно- го обеспечения могут быть четко отделены от деталей реализа- ции, связанных с конкретной машиной. Такой подход дает чи- тателю отправную точку, с которой он может начать проекти- рование системного программного обеспечения для новой или ранее незнакомой ему ЭВМ. Каждая основная глава книги (гл. 2—6) начинается с опи- сания базовых функций обсуждаемого компонента системного !) Утверждение автора не совсем точно. Способы установления связей и передачи параметров между раздельно ассемблированными программами зависят очень заметно от структурных особенностей ЭВМ. — Прим, ред.
14 Гл. 1. Основные понятия обеспечения. Затем рассматриваются машинно-независимые функции, являющиеся расширением базовых. В заключительной части главы содержатся примеры реализации системных про- грамм для реальных машин. Основные главы содержат следую- щие разделы: 1. Основные функции, свойственные данному типу программ- ного обеспечения и не зависящие от реализации. 2. Возможности, наличие и особенности которых тесно свя- заны с машинной архитектурой. 3. Другие возможности, которые имеют относительную ма- шинную независимость и характерны для большинства реали- заций программного обеспечения данного типа. 4. Основные варианты построения конкретных компонентов программного обеспечения (например, однопросмотровый раз- бор в сравнении с многопросмотровым). 5. Примеры реализации для реальных ЭВМ, в которых ос- новное внимание уделяется нестандартным свойствам программ- ного обеспечения, связанным с машинными характеристиками. В данной главе содержатся краткие описания реальных ЭВМ. В дальнейшем эти машины будут использоваться для иллюстрации обсуждаемого материала. Сейчас мы предлагаем вам прочитать эти описания, а затем обращаться к ним, когда это необходимо, при изучении приводимых в каждой главе при- меров. 1.3. Упрощенная учебная машина (УУМ) В этом разделе мы опишем архитектуру нашей УУМ. Эта машина была спроектирована для того, чтобы проиллюстриро- вать наиболее часто встречающиеся аппаратные концепции и возможности, избегая в то же время большинства специфиче- ских особенностей, присущих реальным машинам. Во многих отношениях УУМ похожа на типичную микро- ЭВМ. Подобно многим другим ЭВМ, которые выпускаются про- мышленостью, УУМ имеется в двух вариантах: в виде стан- дартной модели и модели УУМ/ДС (где индекс ДС означает с «дополнительными средствами» или, возможно, за «дополни- тельную стоимость»). Оба варианта спроектированы таким об- разом, чтобы обеспечивалась совместимость «снизу вверх». Это означает, что объектная программа для стандартной модели УУМ будет правильно выполняться и на УУМ/ДС. (Такая совместимость часто встречается в реально существующих се- мействах ЭВМ.) Характеристики стандартной модели УУМ описываются в разд. 1.3.1, а дополнительные возможности, включенные в УУМ/ДС,— в разд. 1.3.2,
1.3. Упрощеная учебная машина (УУМ) 15 1.3.1» Структура УУМ Память Оперативная память состоит из 8-разрядных байтов. Три последовательных байта составляют слово (24 разряда). УУМ имеет байтовую адресацию. Слова адресуются по адресу байта с наименьшим номером. Общий объем оперативной памяти со- ставляет 32 768 (215) байт. Регистры Имеется пять регистров, у каждого из которых есть собст- венное назначение. В таблице, приведенной ниже, указаны мнемонические имена регистров, их номера и назначение. (Си- стема нумерации регистров выбрана таким образом, чтоб-1 была обеспечена совместимость с моделью УУМ/ДС.) Имя Номер Назначение А 0 Сумматор. Используется при выполнении ариф- метических операций X 1 Индексный регистр. Используется для адресации L 2 Регистр связи. Команда перехода на подпро- грамму (JSUB) запоминает в этом регистре ад- рес возврата PC 8 Счетчик команд. Данный регистр содержит ад- рес очередной команды, выбираемой для испол- нения SW 9 Слово состояния. Данный регистр содержит си- стемную информацию, включая код условия (СС — Conditional Code) Форматы данных Значения целого типа хранятся в виде 24-разрядного дво- ичного числа. Для представления отрицательных чисел ис- пользуется дополнительный код. Значения символьного типа хранятся в 8-разрядном коде ASCII (см. приложение Б). Аппаратные средства для выполнения действий над числами с плавающей точкой в стандартной модели УУМ отсутствуют. Форматы команд Все машинные команды стандартной модели УУМ имеют следующий 24-разрядный формат: 8 1 15 код операции(ор) X адрес(addr) Признак х используется для задания индексного способа адре- сации,
ft Гл. 1. Основтаа понятия . Способы адресации Имеются два способа адресации. В команде они задаются разрядом х. Правила вычисления целевого адреса (target address} по адресу, заданному в команде, описываются ниже- следующей таблицей. (Скобки используются для указания на содержимое регистра или ячейки оперативной памяти; напри- мер, (X) обозначает содержимое регистра X.) Способ Признак Вычисление адресации адресации целевого адреса Прямая х = О ТА = addr Индексная х = 1 ТА == addr + (X) Система команд Для решения большинства простых задач вполне достаточно базового набора команд УУМ. Он включает команды загрузки регистра и записи его содержимого в память (LDA, LDX, STA, STX и т. п.), а также команды целочисленной арифметики (ADD, SUB, MUL, DIV). Все арифметические команды выпол- няются над содержимым сумматора и содержимым слова опе- ративной памяти. Результат остается в сумматоре. Специальная команда (СОМР—СОМРаге) служит для сравнения значения, содержащегося в сумматоре, со значением, хранимым в слове оперативной памяти. Эта команда устанавливает код условия СС, являющийся признаком результата сравнения (<, =, >). Команды условного перехода (JLT, JEQ, JGT) проверяют установленное значение СС и выполняют соответствующую пе- редачу управления. Две команды предназначены для организа- ции взаимодействия подпрограмм: JSUB — переход на подпро- грамму с занесением адреса возврата в регистр L; RSUB — возврат по адресу, содержащемуся в регистре L. В приложении А приведен полный список всех команд УУМ и УУМ/ДС с указанием их кодов и описанием выполняемых функций. Средства ввода-вывода В стандартной модели УУМ ввод и вывод выполняются по- байтно. Для обмена используется самый правый байт сумма- тора. Каждому внешнему устройству присвоен уникальный 8-разрядный код. Существует три команды ввода-вывода. Каж- дая из этих команд в качестве своего операнда задает код устройства. Команда проверки состояния устройства (TD —Test Device)) проверяет, готово ли требуемое устройство передать или при- нять очередной байт данных. Для индикации результата про^
1.3. Упрощена я учебная ктптпипя /УУМ.) 17 верки используется код условия. Значение кода условия «<» указывает на готовность устройства к обмену; значение «=» означает, что устройство занято; значение «>» означает, что данное устройство неисправно или не подключено к машине. Программа, желающая выполнить обмен, должна ждать до тех пор, пока устройство не будет готово, и только после этого она может выполнить команду чтения данных (RD — Read Data) или команду записи данных (WD — Write Data). Эга последо- вательность действий должна быть повторена для каждого бай- та данных, участвующего в обмене. Программа, показанная на рис. 2.1 (гл. 2), иллюстрирует такой способ выполнения обмена. 1.3.2. Структура УУМ/ДС Память Структура памяти УУМ/ДС аналогична описанной ранее для УУМ. Однако максимальный объем памяти доступной в УУМ/ДС составляет 1 Мбайт (220 байт). Такое увеличение требует изменения формата команд и способов адресации. Регистры Дополнительные регистры УУМ/ДС приведены в следующей таблице: Имя Номер Назначение В 3 Базовый регистр. Используется для адресации S 4 Общий рабочий регистр. Специаль- ного назначения не имеет т 5 То же F 6 Сумматор с плавающей точкой (48 разрядов) Форматы данных Наряду с форматами данных, которые есть в стандартной модели, УУМ/ДС предоставляет дополнительный формат для данных с плавающей точкой: 1 11 36 s порядок мантисса Мантисса интерпретируется как число между 0 и 1, т. е. предполагается, что двоичная точка стоит непосредственно пе- ре Д, ее старшим разрядом. Для нормализованных чисел стар- ший разряд мантиссы должен равняться единице. Порядок ин- терпретируется как двоичное целое без знака в диапазоне от
18 Гл. 1. Основные понятия О до 2047. Если порядок имеет значение е, а мантисса — значе- ние f, то абсолютная величина числа будет представлена как f X 2<<?“1024) Знак числа с плавающей точкой указывается с помощью зна- чения разряда s (0 — положительное число, 1 — отрицательное число). Машинный нуль представляется в виде слова, содержа- щего нули во всех разрядах. Форматы команд Большой объем оперативной памяти, доступный в УУМ/ДС, означает, что в общем случае 15-разрядного поля для задания адреса будет недостаточно. Таким образом, формат команд стандартной модели УУМ не подходит для УУМ/ДС. Сущест- вует два способа решения этой проблемы: либо использовать относительную адресацию, либо расширять адресное поле до 20 разрядов. Оба эти способа используются в УУМ/ДС (см. форматы 3 и 4 в нижеследующем описании). Кроме того, в УУМ/ДС имеются команды, которые вообще не ссылаются на оперативную память. Для этих команд используются форматы 1 и 2. Ниже приведены форматы команд УУМ/ДС. Значения раз- рядов-признаков в форматах 3 и 4 будут рассмотрены при об- суждении способов адресации. Разряд ё используется для того, чтобы различать форматы 3 и 4 (е = 0 —формат 3, е=1 — формат 4). В приложении А для каждой команды УУМ/ДС указан номер ее формата. Формат 1 (1 байт): 8 код операции (ор) Формат 2 (2 байт). Ь 4'4 код операции(ор) г! г2 Формат 3 (3 байт): 6 1 1 1 t 1 1 12 код операции (ор) п i X Ь Р е смещение (disp) Формат 4 (4 байт): 6 1 t т 1 1 t 20 код операции(ор) п г X b р е адрес (addr) I
1.3. Упрощения учебная машина (УУМ) 19 Способы адресации По сравнению со стандартной моделью в УУМ/ДС реали- зованы два новых способа относительной адресации, для кото- рых используются командный формат 3. Их суть описывается следующей таблицей: Способ Признаки адресации адресации Вычисление целевого адреса Относительно базы Относительно счетчика команд Ь = 1 р = 0 ь = о р= 1 ТА = (В) + disp (0 < disp < 4095) ТА = (PC) + disp (-2048 < disp < 2047) Для способа адресации относительно базы поле смещения в командном формате 3 интерпретируется как 12-разрядное целое без знака. Для способа адресации относительно счетчика команд это поле интерпретируется как 12-разрядное целое со знаком, причем отрицательные величины представляются в до- полнительном коде. Если в командном формате 3 разряды b и р одновременно установлены в 0, то в качестве целевого адреса используется значение поля disp. Для командного формата 4 разряды b и р должны оба равняться нулю, а целевой адрес берется из поля адреса команды. Мы будем называть такой способ пря- мой адресацией в отличие от относительной адресации, описан- ной выше. Каждый из этих способов адресации может соче- таться с индексированием адреса. Признаком индексирования является значение разряда х, равное 1 , и в этом случае выражение для вычисления целевого адреса содержит допол- нительное слагаемое (X). Заметим, что стандартная модель УУМ использует только прямую адресацию (с индексированием или без него). Разряды i и п в форматах 3 и 4 определяют способ исполь- зования целевого адреса. Если i = 1, а п = 0, то собственно зна- чение целевого адреса используется в качестве операнда без выполнения каких-либо дополнительных ссылок в оперативную память. Такой способ задания операнда называется непосред- ственной адресацией. Если i — 0, а п — 1, то содержимое слова по целевому адресу используется в качестве адреса операнда. Такой способ называется косвенной адресацией. Если разряды i и п оба равны 0 или 1, то целевой адрес задает местона- хождение операнда. Мы будем называть такой способ простой
20 Гл. 1 Основные понятия адресацией. При использовании непосредственной и косвенной адресации использование индексирования невозможно. Многие авторы используют термин исполнительный адрес ^effective address) обозначения того, что мы назвали це- • « 9 • а • 3030 003600 * * • ♦ 3600 103000 4 ♦ « 9 ♦ 9 6390 оосзоз • 9 9 9 9 • • СЗОЗ 003030 а а • • а {В} i 006000 (PC) ® 003000 (X) - 00009Q левым адресом команды. Однако име- ется определенное противоречие, ко- гда термин «исполнительный адрес» употребляется для команд, использу- ющих косвенную адресацию. Для то- го чтобы избежать путаницы, мы в данной книге используем термин «це- левой адрес». Команды УУМ/ДС, в которых не используется непосредственная или косвенная адресации, переводятся ассемблером в машинные коды, име- ющие в разрядах i и п значение 1. Ассемблер стандартной модели УУМ устанавливает в этих разрядах нуле- вые значения (так как 8-разрядный код операций всех команд стандарт- ной модели заканчивается кодом 00). В УУМ/ДС предусмотрены специаль- ные аппаратные средства для обеспе- чения упомянутой ранее совместимо- сти «снизу вверх». Если оба разряда i и п имеют нулевое значение, то Машинная команда ‘доеотнадцд* ..... .......t. > знамение, теричное ...... Двоичное загружаемой ' ’,л" 'а г ’ —-— —---------------—------------—--------- в остисто « ’ — —а ор п 1 X ь р е смещение/адрес Целевой адрес в регистр А ©32600 000000 1 1 0 0 1 0 ОНО 0000 0000 <3600 1030QO ©зсзоо 000000 1 I 1 1 0 0 ООН Оооо 0000 6390 оосзоз ©22030 000000 X 0 0 0 1 0 0000 ООП оооо 303G 103000 ©10О30 000000 0 1 0 0 0 0 ОООО ООП 0000 30 D00030 ©03600 оооооо 0 0 0 0 1 1 0110 оооо ооо.о 3600 103000 ©51QC303 оооооо 1 1 0 0 0 1 0000 11Q0 ООП 0000 00X1 0303 003030 б Рис. 1.1. Примеры команд и способов адресации УУМ/ДС. разряды Ь, р, е рассматриваются как часть поля адреса (а не как признаки способа адресации). Таким образом, командный формат 3 становится идентичным командному формату, кото- рый используется в стандартной модели УУМ, что и обеспечи- вает требуемую совместимость. На рис. 1.1 приведены примеры различных способов адре- сации, которые обеспечиваются УУМ/ДС. На рис. 1.1а показа-
1.4. Структура System/370 21 но содержимое регистров В, PC и X, а также некоторых спе- циально подобранных ячеек оперативной памяти. Значения при- ведены в шестнадцатеричном виде. Машинный код команды загрузки сумматора, ее целевой адрес и загружаемое значение показаны на рис. 1.16. Вам следует тщательяо изучить эти при- меры и убедиться в том, что вы правильно понимаете различ- ные способы адресации. В приложении А дается полное описание всех форматов команд и способов адресации, используемых в УММ/ДС. Система команд УУМ/ДС обеспечивает выполнение всех команд стандартной модели. Дополнительно введены команды загрузки и запомина- ния содержимого новых регистров (LDB, STB и др.), а также команды арифметики с плавающей точкой (ADDF, SUBF, MULF, DIVF). Имеются команды, использующие в качестве операндов значения регистров. К ним наряду с командой пере- сылки содержимого одного регистра в другой регистр (RMO)’ относятся арифметические команды, выполняющие действия над содержимым регистров (ADDR, SUBR, MULR, DIVR). Кроме того, введена специальная команда для обращения к суперви- зору (SVC). Выполнение этой команды вызывает прерывание, которое может быть использовано для связи с операционной системой. Подробно организация обращений к супервизору и прерывания будут обсуждаться в гл. 6. Имеются и другие дополнительные команды. Полный список всех команд УУМ/ДС с указанием их кода операции и выпол- няемых функций приведен в приложении А. Средства ввода-вывода Команды ввода-вывода, рассмотренные нами для стандарт- ной модели, выполняются и на УУМ/ДС. Кроме того, данная модель имеет каналы ввода-вывода, которые могут работать независимо от центрального процессора. Это позволяет совме- стить операции обмена с процессом вычислений и тем самым повысить общую эффективность системы. Для начала (start), проверки (test) и прекращения (halt) канальных операций обмена используются соответственно команды SIO, ТЮ и НЮ. Более детально эти вопросы будут обсуждаться в гл. 6. 1.4. Структура System/370 В этом разделе дается краткое введение в System/370. Sy-: stem/370 представляет собой скорее архитектуру, нежели кон- кретную ЭВМ. Эта архитектура, реализованая на ряде раз- личных машин, представляет модели семейства 370, Хотя эти
22 Гл. 1. Основные понятия модели различаются в аппаратной части и по другим физиче- ским характеристикам, они логически совместимы друг с дру- гом. Любая программа должна дать одинаковые результаты на любой ЭВМ семейства 370 при условии, что ее выполнение не связано с временными или другими машинно-зависимыми ха- рактеристиками. Архитектура System/370 совместима снизу вверх с машинами System/360, т. е. программы для System/360 должны правильно выполняться на ЭВМ System/370 (при вы- шеназванных условиях). Весьма похожие архитектуры были реализованы на некоторых других процессорах фирмы IBM и процессорах других фирм. В этом разделе приводятся основные сведения, которые не- обходимы, чтобы понять примеры программного обеспечения System/370, обсуждаемые в дальнейшем. Дополнительную ин- формацию можно найти в IBM [1983] и Страбл [1983]. Память Память состоит из 8-разрядных байтов; каждый байт имеет уникальный адрес. Группа из последовательных 2 байт, первый из которых имеет адрес, кратный 2, называется полусловом. Аналогично группа из последовательных 4 байт, начинающаяся с адреса, кратного 4, называется словом, а группа из 8 байт, начинающаяся с адреса, кратного 8,— двойным словом. Таким образом, полуслово имеет длину 16 разрядов, слово — 32 и Байтовые адреса 01 2 3 4 5 6 7 8 9 А В С D Е F 10 Я 12 Полуслова —i Слова i Л Л И ,._y_ J ♦ • • Рис. 1.2. Деление памяти System/370 на байты, полуслова, слова и двой- ные слова. Двойные слова L двойное слово — 64 разряда. Рис. 1.2 иллюстрирует такое раз- деление памяти на байты, полуслова, слова и двойные слова. Машинные команды должны быть обязательно выравнены по границе полуслова (т. е. должны начинаться с адресов, кратных 2). В большинстве случаев операнды команд могут располагаться в памяти с любого адреса. Например, 16-разряд- ный операнд в команде АН (Add Halfword) может начинаться с любого места, т. е. он не должен выравниваться по границе полуслова. Однако скорость выполнения команд значительно выше, если операнды выравнены по границам, соответствующим их длинам. Максимальный объем оперативной цамяти, который обычно доступен в System/370, составляет 16 Мбайт (224 байт)*
1.4. Структура System/370 23 Регистры В 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. Четыре старших разряда последнего байта могут ин- терпретироваться как знак. Обычно для положительных чисел эти разряды содержат шестнадцатеричный код С, для отрица- тельных — код D и для целых без знака (которые рассматри-
24 Гл. 1. Основные понятия ваются как положительные) — код F. Например, десятичное целое +53 842 представляется в зонном десятичном формате как шестнадцатеричное F5F3F8F4C2 (5 байт), а—6071 — как F6F0F7D1 (4 байт). В упакованном десятичном формате каж- дый байт делится на два 4-разрядных поля. Во всех байтах, за исключением последнего, каждое из этих полей содержит двоичное представление десятичной цифры. В каждом байте Первое поле содержит десятичную цифру, а второе — знак (по схеме, описанной выше). Таким образом, десятичное целое +53 842 представляется в упакованном десятичном формате как шестнадцатеричное 53 842С (3 байт), а — 6071 — как 06071D (3 байт). Величины с плавающей точкой представляются в одном из следующих форматов: Короткий формат (слово): 1 7 24 S е f Длинный формат (двойное слово): 3 7~56 s е f Расширенный формат (два последовательных двойных сло- ва): В любом случае величина числа представляется с помощью мантиссы f и порядка е. Порядок интерпретируется как двоич- ное целое без знака. Мантисса — как шестнадцатеричное без знака, у которого «шестнадцатеричная точка» расположена не- посредственно перед самой левой шестнадцатеричной цифрой. В нормализованных числах с плавающей точкой старшая ше- стнадцатеричная цифра отлична от нуля. Для расширенного формата с плавающей точкой порядок е формируется как кон- катенация порядков el и е2, а мантисса f — как конкатена- ция fl и f2. Абсолютная величина представляемого таким об- разом числа равна f * 16<e-64>
1.4. Структура System/370 25 Знак числа определяется значением разряда s (0 — положи- тельное, 1 — отрицательное). Число нуль представляется кодом; содержащим нули во всех разрядах. Отметим, что порядок чисел с плавающей точкой в Sy- stem/370 интерпретируется как степень 16 (а не 2, как в боль- шинстве машин). Это сделано для того, чтобы увеличить поря- док чисел, которые могут быть представлены с помощью одно- го 32-разрядного слова. Поэтому мантиссу следует интерпрети- ровать как шестнадцатеричное (а не двоичное) число. Это озна- чает, что три старших разряда мантиссы могут равняться нулю '(если старшая шестнадцатеричная цифра равна 1). В силу этого количество значащих разрядов в мантиссе может быть различным в зависимости от величины числа. Форматы команд В System/370 имеется восемь основных форматов команд. В этом разделе мы кратко остановимся на трех наиболее об- щих из них. Детальное описание всех восьми форматов можно найти в IBM [1983]. У большинства команд System/370 есть два операнда. Для этих команд существуют три возможных варианта расположе- ния операндов в памяти: оба операнда в регистрах; один в ре- гистре, другой в памяти; оба в памяти. Следующие форматы команд являются типичными для каждого из этих случаев. . Формат RR: 8 4 4 Ор г1 г2 Формат RX: .8 4 4 4 12 ор Н х2 Ь2 d2 Формат SS: В формате RR оба операнда находятся либо в регистрах об- щего назначения, либо в регистрах с плавающей точкой. Номер регистра кодируется в команде (как rl или г2). Тип регистра определяется кодом операции. Для команд формата RX один операнд находится в регистре rl, а другой — в оперативной памяти. Для задания полного адреса в System/370 требуется 24 разряда. Для того чтобы уменьшить объем памяти, занима-
26 Гл. 1. Основные понятия емый командой, исполнительный адрес (в общем случае) за- дается с помощью базового регистра 62, индексного регистра х2 и 12-разрядного смещения d2. (Порядок вычисления целево- го адреса для каждого способа адресации будет описан ниже.) В формате SS оба операнда расположены в памяти. Если команда данного типа ссылается на операнды переменной дли- ны, то длина операндов задается в команде с помощью полей 11 и 12. Адреса операндов определяются с помощью базового регистра и смещения. Использование индексного регистра за- прещено. В некоторых других форматах команд поле кода операции расширено до 16 разрядов (форматы RRE и SSE). Формат RX можно изменить так, чтобы разрешить использование второго регистрового операнда вместо индексного регистра (формат RS) или чтобы включить 8-разрядный непосредственный операнд вместо двух номеров регистров (формат SI). Одна из модифи- каций формата SS определяет 8-разрядный код, который ис- пользуется в обоих операндах. Другая модификация имеет только один операнд. Способы адресации В System/370 команды, которые ссылаются на операнды, расположенные в оперативной памяти, должны использовать относительную адресацию с базированием. Целевой адрес по- лучается как сумма содержимого базового регистра, индексного регистра (если он задан), и смещения. Смещение интерпретиру- ется как 12-разрядное целое без знака (отрицательное смеще- ние запрещено). Регистр общего назначения R0 нельзя исполь- зовать как базовый или индексный регистр. Если в команде задан в качестве базового или индексного регистра регистр с номером нуль, то при вычислении целевого адреса соответству- ющий регистр использоваться не будет. В System/370 применя- ются два способа непосредственной адресации. В команде LA (Load Address) целевой адрес загружается в заданный регистр •(вместо того чтобы вызывать операнд из оперативной памяти). Некоторые другие команды, например такие, как MVI (MoVe Immediate), используют непосредственый однобайтовый опе- ранд прямо из команды. Однако это особые случаи, когда не- посредственная адресация определяется как часть команды В большинстве команд использование непосредственных опе- рандов невозможно. В System/370 не предусмотрено адресации относительно счетчика команд или косвенной адресации. Прямая адресация возможна только в весьма специфическом случае, когда в ка- честве и базового, и индексного регистров используется регистр с нулевым номером. В этом случае в качестве фактического адреса будет взято 12-разрядное смещение. Возможность пря-
1.4. Структура System/370 2Т мой адресации первых 4096 байт оперативной памяти иногда используется в операционной системе и дает определенные пре- имущества. Система команд Команды System/370 делятся на пять классов: общего на- значения, десятичной арифметики, арифметики с плавающей точкой, управляющие и ввода-вывода. Многие команды уста- навливают 4-разрядный код условия для индикации различных ситуаций. Команды условного перехода могут проверить уста- новленный код и выполнить соответствующее ветвление. Пол- ный список команд System/370 с указанием устанавливаемых ими кодов условия можно найти в IBM [1983]. К командам общего назначения относятся команды загруз- ки и запоминания регистров общего назначения, а также команды, обеспечивающие выполнение двоичных арифметиче- ских операций и операций сравнения. Каждая из этих функций может выполняться с помощью нескольких различных команд в зависимости от типа и месторасположения используемых опе- рандов. Например, двоичное сложение выполняется следующи- ми машинными командами: А сложение слова, память — регистр; АН сложение полуслова, память — регистр; AL сложение слова, без знака, память — регистр; ALR сложение слова, без знака, регистр — регистр; AR сложение слова, регистр — регистр. К группе команд общего назначения относятся также команды условного и безусловного переходов, логические операции, команды пересылки данных и многие другие операции. В Sy- stem/370 имеется команда обращения к супервизору (SVC), похожая на аналогичную команду УУМ/ДС. Команды десятичной арифметики могут быть использованы для выполнения арифметических операций над целыми в упа- кованном десятичном формате. Кроме того, имеются команды для преобразования зонного десятичного формата в упакован- ный и наоборот (команды РАСК и UNPACK), а также коман- ды преобразования двоичного целого в упакованный десятич- ный формат и обратно (команды CVD и CVB). Две другие команды предоставляют возможности для редактирования дан- ных, выполняя, например, такие операции, как вставка запятой и десятичной точки в числовые величины. Команды арифметики с плавающей точкой используются для выполнения операций над числами с плавающей точкой (за- грузка и запоминание регистров с плавающей точкой, арифме- тические операции и операции сравнения). Так же как и в случае команд общего назначения, код операции зависит от
28 Гл. 1. Основные понятия типа и месторасположения операндов. Например, группа команд сложения включает следующие команды: AD сложение с нормализацией, длинный формат, память — регистр; ADR сложение с нормализацией, длинный формат, регистр — регистр; АЕ сложение с нормализацией, короткий формат, память — регистр; AER сложение с нормализацией, короткий формат, регистр — регистр; AU сложение без нормализации, короткий формат, память — регистр; AUR сложение без нормализации, короткий формат, регистр — регистр; AW сложение без нормализации, длинный формат, память — регистр; AWR сложение без нормализации, длинный формат, регистр — регистр; AXR сложение с нормализацией, расширенный формат, регистр — регистр. В группу команд управления входят привилегированные команды, предназначенные главным образом для использования в операционной системе. К ним относятся команды для загруз- ки и запоминания PSW и управляющих регистров, команды защиты памяти и многие другие. Некоторые функции, выпол- няемые этими командами, мы рассмотрим при обсуждении опе- рационных систем в гл. 6. Средства ввода-вывода В System/370 обмен с внешними устройствами осуществля- ется с помощью каналов ввода-вывода, похожих на те, что были описаны для УУМ/ДС. Команды ввода-вывода позволяют центральному процессору (ЦП) начать, остановить и проверить каналы, а также выполнить другие управляющие операции. Имеется, кроме того, возможность, позволяющая ЦП выпол- нять прямую побайтную передачу с помощью специального ин- терфейса, не зависящего от каналов. 1.5. Структура ЭВМ VAX Семейство ЭВМ VAX было представлено фирмой DEC в 1978 г. Аббревиатура VAX указывает на одну из наиболее важных особенностей данной архитектуры — виртуальное ад- ресное расширение (Virtual Address extension). Хотя многие другие ЭВМ (включая System/370) были модифицированы для
1.5. Структура ЭВМ VAX 29 предоставления виртуальной памяти, система VAX с самого на- чала проектировалась в расчете на виртуальное адресное про- странство. Виртуальная память позволяет программам работать так, каик будто они имеют доступ к очень большой памяти, вне зависимости от объема реальной оперативной памяти, исполь- зуемой в системе. Заботу об управлении памятью берет на себя операционная система. Мы обсудим виртуальую память в свя- зи с изучением операционных систем в гл. 6. При проектировании архитектуры ЭВМ VAX была преду- смотрена совместимость с более ранним семейством ЭВМ PDP-11. Средства совместимости обеспечены на уровне аппара- туры, что позволяет выполнять без каких-либо изменений мно- гие программы PDP-11 на ЭВМ VAX. Более того имеется воз- можность одновременной работы в многопользовательском ре- жиме программ для PDP-11 и VAX. В этом разделе дается сводная информация о некоторых основных характеристиках архитектуры ЭВМ VAX. Дополни- тельную информацию можно найти в DEC [1981] и Баас |[1983]. Память Память ЭВМ VAX состоит из 8-разрядных байтов, каждый из которых имеет свой адрес. Два последовательных байта со- ставляют слово (word), четыре — длинное слово (longword), восемь — квадрослово (quadword), шестнадцать — октослово (octaword). Как и в System/370, для уменьшения времени до- ступа к оперативной памяти желательно, чтобы слова, длинные слова, квадрослова и октослова были выравнены по соответ- ствующей границе. Реальная оперативная память ЭВМ VAX может достигать 223 байт. В то же время все программы VAX работают в вир- туальном адресном пространстве объемом 232 байт. Объем ре- альной оперативной памяти обычно никак не влияет на выпол- нение прикладных программ. Половина виртуального адресного пространства называется системным пространством (system space). Эта часть содержит программы операционной системы и используется совместно всеми программами. Другая половина адресного пространства называется пространством процессов [(process space). Пространство процессов определено отдельно для каждой программы. Часть этого пространства содержит стеки, доступные программе. Для работы со стеками имеются специальные регистры и команды. Регистры В ЭВМ VAX есть 16 регистров общего назначения, которые обозначаются как R0 — R15. В то же время некоторые из этих регистров имеют специальные имена и назначение, Длина каж-
’80 Гл. 1. Основные понятия дого регистра общего назначения — 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 принципиально не отличаются от форматов, рассмотренных ра-
1.5. Структура ЭВМ VAX 31 нее. Величина числа с плавающей точкой представляется как мантисса, умноженная на соответствующую степень 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 предусмотрены разнообразные способы адре- сации. За небольшим исключением, каждый из этих способов адресации может использоваться в любой команде. Операнд мо- жет задаваться либо непосредственно в регистре, либо по ад- ресу, находящемуся в регистре. Если операнд задается по ад- ресу, находящемуся в регистре, то после выполнения команды содержимое регистра может автоматически увеличиваться или уменьшаться на длину операнда. Предусмотрено несколько спо- собов адресации относительно базы, в которых могут исполь-
32 Гл. 1. Основные понятия зоваться поля смещения различной длины. Если этот способ адресации используется с регистром PC, то мы получим спо- соб адресации относительно счетчика команд. Каждый из этих способов адресации может включать в себя индексный регистр, а многие из них могут использоваться для определения кос* венной адресации. Наконец, разрешены непосредственные опе- ранды и различные способы адресации, предназначенные для специальных целей. Дальнейшую информацию можно найти в DEC [1981]. Система команд Одной из целей разработчиков системы VAX было создание системы команд, симметричной в отношении различных типов данных. Значительная часть мнемонических имен команд фор* мируется с помощью комбинации следующих элементов: — префикса, определяющего тип операции; — суффикса, определяющего тип операндов; — модификатора (в ряде команд), задающего число one* рандов. Например, команда ADDW2 является операцией сложения с двумя операндами, каждый из которых занимает длинное слово. Аналогично команда MULL3 является операцией умножения с тремя операндами, каждый из которых занимает одно слово. Команда CVTWL определяет операцию преобразования слова в длинное слово (в последнем случае подразумевается исполь- зование двух операндов). В большинстве команд операнд мо- жет располагаться в регистре, в памяти или непосредственно в самой команде. Один и тот же код операции используется не- зависимо от месторасположения операндов. Этот подход явля- ется более гибким по сравнению с подходом в System/370, ко- торый требует различных кодов операций в зависимости от месторасположения операндов. VAX обеспечивает все обычные типы команд для вычисле- ний, пересылки данных, преобразования, сравнения, ветвления и т. п. Кроме того, существует ряд более сложных команд, чем те, которые имеются в большинстве ЭВМ. Во множестве слу- чаев такие операции являются аппаратной реализацией часто используемых последовательностей команд для увеличения эф- фективности и скорости их выполнения. Например, VAX предо- ставляет команды групповой загрузки и запоминания регистров, а также команды для работы с очередями и строками битов переменной длины. Предусмотрены мощные команды для вы- зова и возврата из процедур. С помощью одной-единственной команды осуществляется запоминание содержимого заданной группы регистров, передача списка фактических параметров процедуре, управление указателями стека, фрейма и спискд
1.6. Структура ЭВМ CYBER 33 аргументов, а также установка маски для предотвращения ошибок в арифметических операциях. Более детальную инфор- мацию о системе команд VAX можно найти в DEC [1981], Средства ввода-вывода ; Обмен в ЭВМ VAX выполняется с помощью контроллеров ввода-вывода. Каждый контроллер имеет набор регистров со- стояния/управления и регистров данных, задающих простран- ство в физической оперативной памяти. Участок адресного про- странства, на которое указывают регистры контроллера, назы- вается пространством ввода-вывода. Никаких специальных команд для обеспечения доступа ре- гистров контроллера к пространству ввода-вывода не требует- ся. Драйвер ввода-вывода выполняет команды контроллера, за- поминая значения в нужных регистрах, точно так же, как если бы они были физическими областями памяти. Установление со- ответствия между адресами в пространстве ввода-вывода и фи- зическими регистрами устройства управления осуществляется подпрограммой управления оперативной памятью. 1.6. Структура ЭВМ CYBER В этом разделе мы рассмотрим архитектуру ЭВМ серий CYBER 70 и CYBER 170 фирмы CDC. Несмотря на различия моделей этих серий в аппаратной части, они совместимы на уровне программ. Структура ЭВМ CYBER в значительной сте- пени схожа с архитектурой ЭВМ серии CDC 6000 с некоторы- ми дополнительными возможностями. Похожая архитектура реализована в ЭВМ серии CYBER 180, объявленной фирмой CDC в 1984 г. Эти машины имеют другую длину слова и зна- чительные улучшения в аппаратной части, однако предусмат- ривают возможность для работы в режиме CYBER 70 и CY- BER 170. 4 CYBER — это мультипроцессорная система. В ее состав вхо- дят центральный процессор (ЦП) и несколько периферийных процессоров (ПП). Обычно ЦП занят обработкой пользователь- ских программ, в то время как ПП выполняют функции опера- ционной системы. ПП может начать или прервать выполнение программы, осуществляемое ЦП, для выполнения управляющие функций. Каждый ПП имеет доступ к оперативной памяти ЦП и к устройствам ввода-вывода. Кроме того, каждый ПП имеет свою собственную память. Структура памяти, регистры и система команд ПП и ЦЦ абсолютно разные и никак не связаны друг с другом. В то же время они могут обмениваться информацией через общую 2 Зак, 792
34 Гл. 1. Основные понятия память. В этом разделе мы коснемся лишь характеристик ЦП, Подробную информацию о структурах ЦП и ПП можно найти в CDC [1981а] и Гришман [1974]. Память Память центрального процессора CYBER состоит из 60-раз* рядных слов. Машина имеет словную адресацию. К полям вну- три слова нет средств прямого доступа, за исключением не- большой группы команд, ориентированных на обработку сим- вольной информации. Максимальный объем оперативной памяти 256К (218) слов. Регистры Программа пользователя может работать с тремя группами регистров: А, В и X. В каждой группе имеется по восемь ре- гистров, которые соответственно обозначаются АО — А7, ВО — В7 и ХО —Х7. Длина регистров групп А и В — 18 разрядов. Обычно регистры группы А используются для адресации, а ре- гистры группы В — в качестве индексных регистров или для хранения небольших целочисленных значений (например, счет- чик цикла). Регистр ВО всегда содержит нулевой код. Длина регистров группы X — 60 разрядов. Они используются при вы- полнении большинства операций, а также для хранения вели- чин, выбранных из оперативной памяти. Между регистрами группы А и регистрами группы X уста- новлено не совсем обычное соответствие. Если в регистр А1 заносится некоторое значение, то оно рассматривается как ад- рес слова в оперативной памяти, и в регистр XI автоматически заносится содержимое слова оперативной памяти по этому ад- ресу. Такое же логическое соответствие существует между регистрами А2 — А5 и регистрами Х2 — Х5. Если некоторый адрес заносится в регистр А6, то содержимое регистра Х6 авто- матически записывается в оперативную память по этому адре- су. Аналогичное соответствие установлено между регистрами А7 и Х7. Регистры АО и ХО логически между собой не связаны. Форматы данных Целые величины хранятся в виде 60-разрядных двоичных чисел (хотя некоторые операции целочисленной арифметики используют только младшие 48 разрядов слова). Для предо- ставления отрицательных чисел используется обратный код. Символьная информация хранится в 6-разрядном представле- нии, которое называется дисплейным кодом CDC. Вследствие использования 6-разрядного символьного кода содержимое сло- ва ЭВМ CYBER обычно представляется в виде восьмеричного (а не шестнадцатеричного) числа*
1.6. Структура ЭВМ CYBER 35 Числа с плавающей точкой представляются в следующем формате: 1 И 48 SB с Коэффициент с интерпретируется как 48-разрядное двоичное целое. Предполагается, что двоичная точка расположена непо- средственно после младшего разряда коэффициента. Для нор- мализованных чисел старший разряд коэффициента должен со- держать 1. Порядок е интерпретируется как ll-разрядное дво- ичное целое без знака. Абсолютная величина числа с плаваю- щей точкой может быть записана в виде С * 2(е-Ю24) Знак числа определяется разрядом s. Для положительных чи- сел он равен 0, Отрицательные числа представляются инверти- рованием всех разрядов слова. Величина нуль с плавающей точкой записывается в виде слова, имеющего нули во всех 60 разрядах. Некоторые значения порядка зарезервированы и не исполь- зуются для обычных чисел с плавающей точкой. Такие зарезер- вированные значения порядка используются для представления положительной и отрицательной бесконечности (результат де- ления числа, отличного от нуля, на нуль) и для представления положительной и отрицательной неопределенности (результат деления нуля на нуль). Форматы команд Для большинства команд ЦП ЭВМ CYBER используются следующие форматы: ^15-разрядный формат) : е ззз ор i J k ['(30-разрядный формат) : 633- 18 op j J addr В этих форматах ор является кодом операции. Регистры, ис- пользуемые в качестве операндов, обозначены как i, j, k. Тип регистров определяется командой. Если один из операндов рас- положен в оперативной памяти, то необходимо использовать 30-разрядный формат. Поле addr в этом формате содержит пол-, ный 18-разрядный адрес. 2*
36 Гл. 1. Основные понятия Для некоторых команд поле кода операции логически рас- ширено до 9 разрядов за счет поля i. В других командах поля i и k составляют одно поле, в котором могут задаваться маска или величина сдвига. Кроме того, есть специальный 60-разряд- ный формат для небольшой группы команд, ориентированных на обработку символьной информации. Используемые в этом формате поля различны для разных команд. Детальное описа- ние этих форматов можно найти в CDC [1982а], Способы адресации В ЭВМ CYBER предусмотрен единственный способ адре- сации. Для доступа к оперативной памяти необходимо поме- стить полный 18-разрядный адрес в регистр группы А, что обеспечивает загрузку соответствующего регистра группы X словом из оперативной памяти или запись его содержимого в память. Поскольку используются реальные адреса оперативной памяти, то этот процесс во многом похож на способ прямой адресации, который мы обсуждали ранее. Однако команда установки регистра А может также выполнять вычисления, включающие до трех операндов. Поэтому программист может реализовать широкий набор различных способов вычисления целевого адреса, включая способы, эквивалентные косвенной и индексной адресации, а также адресации относительно базы. Кроме того, предусмотрены средства для непосредственной адресации. Система команд Логически команды ЦП CYBER распадаются на несколько групп. Самая большая и наиболее часто используемая содер- жит команды установки регистров А, В и X. Иллюстрацией не- которых из возможных команд этой группы могут служить сле- дующие команды: SAi Aj + Bk SAi адрес SBi Bj + K SXi Xj + Bk Так, например, команда SA3 A4 + B1 помещает в регистр АЗ сумму содержимого регистров А4 иВ1. В свою очередь это вызовет загрузку регистра ХЗ содержимым слова оперативной памяти по адресу, занесенному в АЗ, Команда SA6 ВЕТА
1 6. Структура ЭВМ CYBER 37 (где ВЕТА —это метка, имеющая в качестве своего значения адрес в оперативной памяти) заносит значение ВЕТА в регистр А6, что обеспечивает запоминание содержимого Х6 по этому адресу. Команды установки могут заносить значение в регистр любой группы (А, В или X) и имеют различные возможности для задания своих операндов. Команды установки выполняют вычисления над 18-разряд- ными величинами, даже если установка производится в регистр группы X. Существует другая группа команд, которая обеспе- чивает выполнение сложения и вычитания 60-разрядных це- лых, арифметику с плавающей точкой, сдвиги и логические операции над X регистрами. Кроме того, есть четыре команды, предназначенные для работы с символами. С помощью этих команд можно осуществить пересылку строк символов в опера- тивной памяти и выполнить посимвольное сравнение строк. В ЭВМ CYBER предусмотрены команды безусловного пе- рехода и два типа команд условного перехода. Команды перво- го типа проверяют значение, содержащееся в регистре X, и осуществляют передачу управления в зависимости от резуль- тата. Команды второго типа обеспечивают передачу управления в зависимости от результата сравнения содержимого двух ре- гистров группы В. Понятия код условия в ЭВМ CYBER нет. Сравнение и передача управления осуществляются одной командой. Предусмотрена специальная команда передачи уп- равления с возвратом для вызова подпрограмм. Адрес возврата хранится в памяти в первой команде вызываемой подпро- граммы. Средства ввода-вывода Все операции обмена выполняются периферийными процес- сорами. Как это делается, а также некоторые другие общие во- просы взаимодействия между ЦП и ПП мы обсудим в гл4 6,
Глава 2. Ассемблеры В этой главе будут рассмотрены вопросы проектирования и реализации ассемблеров. Существует целый ряд основных функ- ций, например таких, как трансляция мнемонических кодов операций в их эквиваленты на машинном языке или присваи- вание машинных адресов символическим меткам, которые долж- ны выполняться любым ассемблером. Если мы будем рассмат- ривать эти основные функции, то большинство ассемблеров окажутся весьма похожими. Однако за пределами этого базового уровня возможности, предоставляемые ассемблерами, а также схемы их построения сильно зависят как от входного языка, так и от языка маши- ны. Одним из аспектов такой машинной зависимости, естествен- но, являются имеющиеся различия в форматах машинных команд и кодах операций. Как мы увидим в дальнейшем, суще- ствуют и менее явные зависимости между ассемблерами и ар- хитектурой ЭВМ. С другой стороны, некоторые средства языка ассемблера (и соответствующих ассемблеров) не имеют прямой связи со структурой машины. Их выбор является в известном смысле произвольным и определяется разработчиком языка ас- семблера. Вначале мы рассмотрим базовый ассемблер для стандартной модели нашей упрощенной учебной машины. В разд. 2.1 рас- сматриваются наиболее важные операции, выполняемые типо- вым ассемблером, и описываются общие методы их реализации. Приведенные здесь алгоритмы, структуры данных и др. ис- пользуются в большинстве ассемблеров. Таким образом, дан- ный уровень описания дает нам отправную точку для изучения более сложных средств, предоставляемых ассемблерами. Мы также можем использовать эту базовую структуру как каркас при разработке нового ассемблера. В разд. 2.2 мы изучим некоторые типичные расширения ба- зовой структуры, которые диктуются аппаратными особенностя- ми. Это будет сделано на примере ассемблера для УУМ/ДС, Конечно, ассемблер УУМ/ДС не покрывает все возможные ма- шинно-зависимые свойства, однако он включает некоторые сред- ства, которые наиболее часто встречаются в реальных маши- нах. Рассматриваемые здесь принципы и технические приемы могут быть легко применены к другим ЭВМ. В разд. 2.3 обсуждаются наиболее часто встречающиеся ма- шинно-независимые расщирения языка ассемблера и способы
2.1. Основные функции ассемблера 39 их реализации. Опять-таки нашей целью является не рассмо- трение всех возможных вариантов, а обсуждение концепций и технических приемов, которые могут быть использованы при разработке нового ассемблера. В разд. 2.4 обсуждаются некоторые важные варианты схем ассемблирования. Схема ассемблирования относится к тем ха- рактеристикам ассемблера, которые не находят своего отраже- ния в самом языке ассемблера. Мы рассмотрим однопросмот- ровые и многопросмотровые ассемблеры, а также двухпросмот- ровые ассемблеры с оверлейной структурой. Мы также коснем- ся вопросов реализации таких ассемблеров и обсудим ситуации, в которых они могут быть полезны. Наконец, в разд. 2.5 будут вкратце рассмотрены некоторые примеры ассемблеров для реальных машин. Мы не будем стре- миться к детальному обсуждению всех аспектов. Вместо этого мы концентрируем наше внимание на наиболее интересных возможностях, которые являются следствиями тех или иных аппаратных или программных решений. 2.1. Основные функции ассемблера На рис. 2.1 приведена программа на языке ассемблера ба- зовой модели УУМ. Различные варианты этой программы мы будем использовать на протяжении всей этой главы для иллю- страции различных свойств ассемблера. Номера строк исполь- зуются только для ссылок и не являются частью программы. Эти номера помогают также установить соответствие между участками различных вариантов программы. Мнемонические имена команд, используемые в примере, были рассмотрены в разд. 1.3.1 и приведены в приложении А. Модификатор, «,Х», следующий за операндом (см. строку 160), указывает на исполь- зование индексной адресации. Строки, начинающиеся с •, явля- ются комментариями. Кроме мнемонических имен машинных команд в примере использованы следующие директивы ассемблера: START Определяет имя и начальный адрес программы. 1 END Указывает на конец исходной программы и (обыч- но) задает первую исполняемую команду про- граммы. BYTE Формирует символьную или шестнадцатеричную константу, занимающую столько байтов, сколько необходимо для представления константы. WORD Формирует целую константу, занимающую одно слово. RES В Резервирует заданное количество байтов для дан- ных. RESW Резервирует заданное количество слов для данных,
40 Гл 2. Ассемблеры Строка Исходное предложение 5 COPY START 1000 КОПИРОВАНИЕ ФАЙЛА 10 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА 15 CLOOP JSUB RDREC ВВОД ВХОДНОЙ ЗАПИСИ 20 LDA LENGTH ПРОВЕРКА НА EOF (LENGTH = 0> г>5 COMP ZERO 30 JEQ ENDFIL ВЫХОД, ЕСЛИ НАШЛИ EOF 35 JSUB WRREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ 40 J CLOOP ЦИКЛ 45 EK'DFIL LDA EOF ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА 50 STA BUFFER 55 LDA THREE УСТАНОВИТЬ LENGTH - 3 60 STA LENGTH 65 JSUB WRREC ЗАПИСЬ EOF 70 LDL RE1ADR УСТАНОВКА АДРЕСА ВОЗВРАТА 75 RSUB ВОЗВРАТ ИЗ ПРОГРАММЫ 80 EOF BYTE C'EOF' 85 Th*E£ WORD 3 90 ZERO WORD 0 95 RETADR RESW 1 100 LENGTH RESW 1 ДЛИНА ЗАПИСИ 105 BUFFER RESB 4096 ДЛИНА БУФЕРА - 4096 БАЙТ 110 M 115 « ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР 120 a. 125 RDREC LDX ZERO ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 130 LDA ZERO ОБНУЛЕНИЕ РЕГИСТРА А .135 RLOOP TD INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА 140 JEQ RLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А . 150 COMP ZERO ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х'00 155 JEQ EXIT ВЫХОД ИЗ ЦИКЛА ПО КОНЦУ ЗАПИСИ 160 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 165 TIX MAXLEN ЦИКЛ ДО ДОСТИЖЕНИЯ 170 JLT RLOOP МАКСИМАЛЬНОЙ ДЛИНЫ 175 EXIT STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ 180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 385 INPUT BYTE X'Fl' КОД УСТРОЙСТВА. ВВОДА 390 MAXLEH WORD 4096 395 200 w ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 210 WRREC LDX ZERO ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 215 WLOOP TD OUTPUT ПРОВЕРКА УСТРОЙСТВА ВЫВОДА 220 JEQ «LOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 225 LDCH BUFFER,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 230 WD OUTPUT ВЫВОД СИМВОЛА 235 TIX LENGTH ЦИКЛ, ПОКА НЕ БУДУТ 240 JLT WLOOP ВЫВЕДЕНЫ ВСЕ СИМВОЛЫ 245 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 250 OITFUT BYTE X'05' КОД УСТРОЙСТВА ВЫВОДА '255 END FIRST Рис. 2.1. Пример программы на языке ассемблера УУМ. Программа состоит из трех подпрограмм. Главная програм- ма вводит записи с устройства ввода (код устройства F1) и копирует их на устройство вывода (код 05). Она вызывает под- программу RDREC для ввода записи на буфер и подпрограм- му WRREC для вывода записи из буфера на устройство выво-
2.1. Основные функции ассемблера 41 да. Так как в УУМ имеются только команды RD и WD, то каждая подпрограмма должна передавать данные побайтно. Буфер необходим для согласования скорости обмена устройства ввода со скоростью выводного устройства (например, магнитно- го диска и устройства печати). В гл. 6 мы увидим, как для выполнения этих же функций используются канальные програм- мы и макрокоманды операционной системы УУМ/ДС. Призна- ком конца записи служит нулевой код. Признаком конца фай- ла служит запись, имеющая нулевую длину. При обнаружении конца файла программа выдает признак конца файла EOF на устройство вывода и заканчивает свою работу возвратом управ- ления вызвавшей программе (возможно, операционной си- стеме). 2.1.1. Простой ассемблер для УУМ На рис. 2.2 показана та же программа, что и на рис. 2.1, но вместе с объектным кодом, сгенерированным для каждого предложения. В столбце «Адрес» даны шестнадцатеричные ма- шинные адреса оттранслированной программы. Мы предполага- ем, что начальный адрес программы равен 1000. Заметим, что в реальном листинге ассемблера комментарии будут, конечно, сохранены. В нашем примере они исключены только из-за не- достатка места. Для перевода исходной программы в ее объектное представ- ление необходимо выполнить следующие действия (не обяза- тельно в указанном порядке): 1. Преобразовать мнемонические коды операций в их экви- валенты на машинном языке — например, перевести STL в 14 (строка 10). 2. Преобразовать символические операнды в эквивалентные им машинные адреса — например, перевести RETADR в 1033 (строка 10). 3. Построить машинные команды в соответствующем фор- мате. 4. Преобразовать константы, заданные в исходной програм- ме, во внутреннее машинное представление (например, в строке 80 оттранслировать EOF в 454F46). 5. Записать объектную программу и выдать листинг. Все указанные действия, за исключением второго, легко мо- гут быть выполнены простой построчной обработкой исходной программы. В то же время трансляция адресов вызывает опре- деленные трудности. Рассмотрим предложение 10 1000 FIRST STL RETADR Мы не можем сразу оттранслировать это предложение, так как не знаем адрес, который будет присвоен метке RETADR,
42 Гл. 2. Ассемблеры Строка Адрес Исходное предложение Объектный код 5 1000 COPY START 1000 10 1000 FIRST STL RETADR 141033 15 1003 CLOOP JSUB RDREC 482039 20 1006 LDA LENGTH 001036 25 1009 COMP ZERO 281038 30 100С JEQ ENDFIL 301015 35 100F JSUB WRREC 482061 40 1012 J CLOOP 3C1003 45 1015 ENDFIL LDA EOF 00102A 50 1018 STA BUFFER 0C1039 55 101В LDA THREE 00102D 60 101Е STA LENGTH 0C1036 65 1021 JSUB WRREC 482061 70 1024 LDL RETADR 081033 75 1027 RSUB 4C0008 80 102А EOF BYTE C'EOF' 454F46 85 1020 THREE WORD 3 000003 90 1030 ZERO WORD 0 000000 95 1033 RETADR RESW 1 100 1036 LENGTH RESW 1 105 1039 BUFFER RESB 4096 110 * 115 ж ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 125 2039 RDREC LDX ZERO 041038 130 2038 LDA ZERO 001038 135 203F RLOOP TD INPUT E0205D 140 2042 JEQ RLOOP 30203F 145 2045 RD INPUT D8205D 150 2048 COMP ZERO 281038 155 204В JEQ EXIT 302057 160 204Е STCH BUFFER,X 549039 165 2051 TIX MAXLEN 2C205E 170 2054 JLT RLOOP 38203F 175 2057 EXIT STX LENGTH 101036 180 205А RSUB 4C0008 185 205D INPUT BYTE X'Fl' Fl 190 205Е MAXLEN WORD 4096 001008 195 ж 200 « ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ ВУФЕРА 205 210 2061 WRREC LDX ZERO 041038 215 2064 WLOOP ТВ OUTPUT E02079 220 2067 JEO WLOOP 302064 225 206А LDCH BUFFERS 509039 230 206В WD OUTPUT BC2079 235 2070 TIX LENGTH 2C1036 240 2073 JLT WLOOP 382064 245 2076 RSUB 4C0000 250 2079 OUTPUT BYTE X'05' 05 255 END FIRST Рис. 2.2. Объектный код для программы на рис. 2.L Поэтому большинство ассемблеров выполняет два просмотра исходной программы. Основной задачей первого просмотра является поиск символическисх имен и назначение им адресов. Фактическая трансляция, описанная выше, выполняется во время второго просмотра.
2.1. Основные функции ассемблера 43 Ассемблер наряду с трансляцией команд исходной програм- мы должен также выполнять и так называемые директивы ас- семблера (иногда их называют псевдокомандами). Эти директи- вы не переводятся непосредственно в машинные команды (хотя и могут оказывать влияние на объектную программу), а управ- ляют работой самого ассемблера. Примерами таких директив могут служить предложения BYTE и WORD, которые служат для включения в объектную программу констант, и предложе- ния RESB и RESW, обеспечивающие резервирование заданного пространства оперативной памяти. В нашей программе имеют- ся и другие директивы — START, которая определяет началь- ный адрес объектной программы, и END, которая отмечает конец исходной программы. В заключительной фазе своей работы ассемблер должен за- писать полученный объектный код на некоторое устройство вы- вода. Позднее эта объектная программа будет загружена в оперативную память для исполнения. Для представления объ- ектной программы мы будем использовать простой формат, в котором определены три типа записей: запись-заголовок, тело программы и запись-конец. Запись-заголовок содержит имя программы, ее начальный адрес и длину. Тело программы со- держит машинные команды и данные программы с указанием адресов их загрузки. Запись-конец отмечает конец объектной программы и определяет адрес, с которого следует начать ис- полнение программы (точку входа). (Данный адрес задается операндом предложения END. Если этот операнд не задан, то в качестве точки входа берется адрес первой исполняемой команды D.) Ниже приведены форматы, которые мы будем использовать для записей объектной программы. Реализация форматов (но- мер столбца и т. п.) может быть любой, однако содержащаяся в них информация должна в той или иной форме присутство- вать в объектной программе. Запись-заголовок: Н Столбец 1 Столбцы 2 — 7 Столбцы 8-—13 Столбцы 14—19 Имя программы Начальный адрес программы (шестна- дцатеричный) Длина программы в байтах (шестнадца- теричная) 1} Начальный адрес 1 первой выполняемой команды (точкой входа). ^Прим, ред, программы не обязательно совпадает $ адресом
44 Гл. 2. Ассемблеры Тело программы: Столбец 1 Т Столбцы 2 — 7 Начальный адрес в данной записи (ше- стнадцатеричный) Столбцы 8 — 9 Длина данной записи в байтах (шестна- дцатеричная) Столбцы 10 — 69 Объектный код (шестнадцатеричный) Запись-конец: Столбец 1 Е Столбцы 2 — 7 Адрес первой исполняемой команды объ- ектной программы (шестнадцатеричный) Для того чтобы избежать путаницы, мы использовали термин столбец (column), а не байт для ссылки на месторасположения внутри записей объектной программы. Это отнюдь не означает, что для хранения объектной программы мы будем использовать перфокарты (или какое-либо другое специальное представ- ление), нлсору ЛооюооЛоаю7А Тл001000л1Ел14ХОЗЗл482039л00103'бл281030л301015л482061лЗС1003л00102^)С1039л00102а ТЛ00101ЕЛ15ЛОС1036Л482061Л081033Л4СООООЛ454Р46Л000003Л000000 Тл002039л1Ел041030л001030лЕ02050л30203^С82050л28103^302057л549039л2С205Ел38203Р TO02O571C1O10364COO0OF1001000041030AE02079A302064A509039ADC2079A2C1036 Л Л Л Л Л Л Л Л Л Л л л Т002073073820644С000005 Л Л Л Л л Ел001000 Рис. 2.3. Объектная программа, соответствующая рис. 2.2. На рис. 2.3 показан объектный код, соответствующий рас- смотренной нами программе (рис. 2.2). На этом и последую- щих рисунках мы будем использовать символ Л для указа- ния границы полей. Конечно, в реальной объектной программе этого символа нет. Обратите внимание, что для адресов 1033— 2038 нет соответствующего объектного кода. Эта память просто резервируется загрузчиком и затем используется программой во время ее исполнения. (Детальное обсуждение функций за- грузчика будет приведено в гл. 3.) Теперь мы можем дать общее описание функций каждого из двух просмотров нашего простого ассемблера. Первый просмотр (определение имен): 1. Назначение адресов для всех предложений исходной программы. 2. Запоминание значений (адресов), присвоенных всем мет- кам, для последующего их использования на втором просмотре.
2.1. Основные функции ассемблера 45 3. Выполнение некоторых директив ассемблера. (К ним от- носятся директивы, влияющие на адресацию, такие как BYTE, RESW и т. п.) Второй просмотр (трансляция команд и генерация объектно- го кода): 1. Трансляция команд (перевод кодов операций и разреше- ние адресных ссылок). 2. Генерация данных, заданных директивами BYTE, WORD и др. 3. Выполнение тех директив ассемблера, которые не были обработаны на первом просмотре. 4. Запись объектного кода и выдача листинга. В следующем разделе мы подробнее остановимся на этих функциях и дадим описание внутренних таблиц ассемблера и полное описание алгоритмов каждого из просмотров. 2.1.2. Таблицы и алгоритмы ассемблера Наш простой ассемблер использует две основные внутренние таблицы: таблицу кодов операций (ОРТАВ) и таблицу симво- лических имен (SYMTAB). ОРТАВ используется для поиска мнемонических кодов операций и перевода их в эквивалентные им представления на машинном языке SYMTAB используется для хранения значений (адресов), присвоенных символическим именам. Еще нам потребуется счетчик адреса (LOCCTR). LOCCTR это переменная, которая используется для назначения адресов. Начальное значение LOCCTR определяется операндом предло- жения START. После обработки очередного предложения исходной программы длина полученной команды или области данных прибавляется к LOCCTR. Таким образом, если мы встре- чаем помеченное предложение исходной программы, то значе- ние этой метки определяется текущим значением LOCCTR. Таблица кодов операций должна по крайней мере содержать мнемонические коды операций и их машинные эквиваленты, В более сложных ассемблерах эта таблица, кроме того , содер- жит информацию о длине и формате каждой команды. Во вре- мя первого просмотра ОРТАВ используется для поиска и про- верки корректности задания кодов операций в исходной про- грамме. Во время второго просмотра она используется для перевода мнемонических кодов в их машинное представление. Фактически в нашем простом ассемблере для УУМ оба эти процесса можно совместить в одном просмотре (неважно, в первом или во втором). Однако для машин, имеющих форматы команд переменной длины (например, УУМ/ДС), нам необхо- димо просматривать ОРТАВ и при первом просмотре для того,
46 Гл. 2. Ассемблеры Pass Is begin прочитать первую входную строку if OPCODE 'START' then begin запомнить «OPERAND! в качестве начального адреса занести начальный адрес в LOCCTR записать строку в промежуточный файл прочитать следующую входную строку end else занести 0 в LOCCTR While OPCODE О 'END' do begin if это не строка-комментарий then begin if есть символ в поле метки then begin поиск метки в SYMTAB if нашли then занести признак ошибки (второе определение) else занести (LABEL,LOCCTR) в SYMTAB end Поиск OPCODE в OPTAB if нашли then прибавить 3 {длина команды) к LOCCTR else if OPCODE ~ 'WORD' then прибавить 3 к LOCCTR else if OPCODE - 'RESW' then < прибавить 3 * «OPERAND! К LOCCTR else if OPCODE * 'RESB' then прибавить «OPERAND! к LOCCTR else if OPCODE ~ 'BYTE' then begin определить длину константы в байтах прибавить длину к LOCCTP end else занести признак ошибки (неверный код операции) end {if не комментарий) записать строку в промежуточный файл прочитать следующую входную строку end (while) записать последнюю строку в промежуточный файл запомнить (LOCCTR - начальный адрес) в качестве длины Программы end (Pass 1> Рис. 2.4а. Алгоритм первого просмотра ассемблера. чтобы определить приращение переменной LOCCTR. При вто- ром просмотре мы используем ОРТАВ для определения форма- та и других специальных характеристик ассемблируемой коман- ды. В нашем случае мы решили придерживаться именно этой структуры, поскольку она типична для большинства ассемб- леров.
2.1. Основные функции ассемблера' 47 Pass 2# begin прочитать первую входную строку Сиз промежуточного файла} if OPCODE - 'START' then begin выдать строку листинга прочитать следующую входную строку end занести запись-заголовок в объектную программу инициализировать первую запись тела программы While OPCODE О 'END' do begin if это не строка-комментарий then begin поиск OPCODE в ОРТАВ if нашли then begin if в поле операнда есть имя-then begin поиск имени в SYMTA if нашли then запомнить значение имени в качестве адреса операнда else begin запомнить 0 в качестве адреса операнда занести признак ошибки (имя не определено* end end (if есть имя} else запомнить 0 в качестве адреса операнда ассемблировать команду в объектное представление end {if нашли? else if OPCODE * 'BYTE' или 'WORD' then преобразовать константу в объектное представление if объектный код не помещается в текущей записи тела программы then begin занести текущую запись в объектную программу инициализировать новую запись тела программы end занести объектный код в запись тела программы end Cif не комментарий) выдать строку листинга прочитать следующую входную строку end {while? занести последнюю запись тела в объектную программу занести запись-конец в объектную программу ( выдать последнюю строку листинга end {Pass 2? Рис. 2.46. Алгоритм второго просмотра ассемблера. Обычно ОРТАВ организуется в виде хеш-таблицы в ко- торой в качестве ключа используется мнемонический код one-?, рации. (Конечно, ОРТАВ строится заранее — при создании ° В нашей литературе иногда используется термин «перемешанные таблицы»* — Прим. ред.
48 Гл. 2. Ассемблеры ассемблера, а не во время его работы.) Такая организация очень удобна, поскольку обеспечивает быстрый поиск при мини- мальном числе сравнений. В большинстве случаев ОРТАВ пред- ставляет собой статическую таблицу, т. е. в процессе работы в ней не создаются новые элементы, а уже определенные не исключаются. В этом случае можно построить специальную хеш-функцию или другую структуру данных, обеспечивающую для конкретного набора ключей оптимальное время доступа. Однако чаще всего используются стандартные хеш-функции. Подробную информацию о построении хеш-таблиц можно най- ти в любом хорошем учебнике по структурам данных, напри- мер в Стендиш [1980] или Кнут [19736]. Таблица имен (SYMTAB) состоит из имен и значений (ад- ресов) всех меток исходной программы вместе с признаками, указывающими на ошибку (например, дважды определенное имя). Эта таблица может также содержать информацию о ти- пе, длине и других характеристиках помеченной области дан- ных или команды. Во время первого просмотра все встретив- шиеся в исходной программе имена вместе с их адресами (они определяются по LOCCTR) сразу заносятся в SYMTAB. Во время второго просмотра имена, используемые в качестве опе- рандов, ищутся в SYMTAB и соответствующие им адреса ис- пользуются для генерации команд. Для повышения эффективности поиска и внесения новых имен SYMTAB обычно организуется также в виде хеш-табли- цы. Поскольку операция исключения элемента из таблицы при- меняется крайне редко (скорее никогда), то вопрос об эффек- тивности ее выполнения не возникает. Интенсивное использо- вание SYMTAB на протяжении всей работы ассемблера требует тщательного подбора хеш-функции. Очень часто программис- ты используют много похожих друг на друга имен, например имена, начинающиеся или заканчивающиеся одними и теми же символами (такие как LOOP1, LOOP2, LOOPA), или имена, имеющие одинаковую длину (А, X, Y, 2*). Важно, чтобы ис- пользуемая хеш-функция обеспечивала хорошую работу с та- кими неслучайными именами. Часто хороший результат дости- гается путем деления входного ключа на простое число, равное длине таблицы. Хотя в качестве входной информации на этапе второго про- смотра можно использовать исходную программу, однако Дол- жна быть учтена и информация, полученная ранее — после пер- вого просмотра (например, адрес предложения или признак ,ошибки). Поэтому обычно во время первого просмотра созда- ется промежуточный файл, который содержит каждое предло- жение исходной программы вместе с его адресом, признаком ршибки и другой необходимой информацией. Промежуточный файл является входным для второго просмотра, Рабочая копия
2.2. Машинно-зависимые характеристики ассемблера 49 исходной программы, содержащаяся в промежуточном файле, может быть использована для того, чтобы сохранить результа- ты некоторых операций, выполненных во время первого про- смотра (например, операции сканирования поля операндов для определения имен и способов адресации). Это дает возмож- ность не выполнять их еще раз при втором просмотре. Анало- гично для кодов операций и имен могут быть сохранены ука- затели на таблицы ОРТАВ й SYMTAB, что позволит избе- жать повторных операций поиска. На рис. 2.4а и 2.46 представлены алгоритмы соответственно первого и второго просмотров нашего ассемблера. Эти алгоритмы, несмотря на то что они описывают простой ассемблер, являют- ся основой для более сложных двухпросмотровых ассемблеров, которые мы рассмотрим позднее. Для простоты мы будем полагать, что исходная программа записана в фиксированном формате с полями МЕТКА, ОПЕРАЦИЯ и ОПЕРАНДЫ. Если какое-либо из этих полей содержит строку символов, пред- ставляющую число, мы будем обозначать это с помощью пре- фикса # (например, # [OPERAND]). На данном этапе очень важно, чтобы вы досконально разо- брались с алгоритмами, представленными на рис. 2.4а и 2.46. Вам настоятельно рекомендуется тщательно разобраться в этих алгоритмах, применив их для ручной трансляции программы на рис. 2.1 в ее объектное представление на рис. 2.3. Конечно, для того чтобы сконцентрировать внимание на об- щей структуре и основных концепциях, мы опустили многие детали алгоритмов. Вам надлежит самостоятельно обдумать их, а также определить те функции ассемблера, которые следует реализовать в виде отдельных процедур или модулей. (Напри- мер, операции «поиск в таблице имен» или «чтение входной строки» являются хорошими кандидатами на такую реализа- цию.) Такой внимательный анализ алгоритмов следует делать всегда, прежде чем приступать к фактической реализации ас- семблера или какого-либо другого элемента программного обес- печения значительного размера, 2.2. Машинно-зависимые характеристики ассемблера В этом разделе на примере ассемблера для УУМ/ДС мы рассмотрим, как расширение аппаратных возможностей ЭВМ влияет на структуру и функции ассемблера, Архитектура мно- гих реальных ЭВМ похожа на структуру УУМ/ДС, Поэтому в значительной степени наше обсуждение применимо Н§ только к УУМ/ДС, но и к реальным машинам.
50 Гл. 2. Ассемблеры На рис. 2.5 показано, как предыдущая программа (см. рис. 2.1) может быть записана с использованием дополнитель- ных возможностей системы команд УУМ/ДС. Косвенная адре- сация в нашем языке ассемблера задается добавлением к опе- ранду префикса @ (строка 70). Непосредственный операнд обо- значается префиксом # (строки 25, 55, 133). Для команд, в которых есть ссылка на оперативную память, обычно исполь- зуется либо адресация относительно счетчика команд, либо адресация относительно базы. Директива ассемблера BASE (строка 13) используется в связи со способом адресации относи- тельно базы. (Подробности и примеры см. в разд. 2.2.1.) В не- которых командах величина смещения в относительном способе адресации может быть очень большой и для ее размещения будет недостаточно поля 3-байтового командного формата. Для таких команд необходимо использовать 4-байтовый расширен- ный командный формат. На языке ассемблера этот формат задается добавлением к коду операции префикса + (см. строки 15, 35, 65). Ответственность за правильность специфи- кации команд расширенного формата возлагается на про- граммиста. Основное отличие новой версии программы от программы для УУМ заключается в том, что в ней всюду, где это воз- можно, используются команды вида регистр — регистр (вместо команд вида регистр — память). Например, в строке 150 вместо предложения COMP ZERO использовано COMPR A, S, а в строке 165 вместо TIX MAXLEN использовано TIXR Т. Кроме того, всюду, где это возможно, используются непосредственные операнды и косвенная адресация (например, строки 25, 55,70), Эти изменения внесены для того, чтобы использовать пре- имущества архитектуры УУМ/ДС с целью увеличения скоро- сти выполнения программы. Операции вида регистр — регистр быстрее, чем соответствующие операции вида регистр — память. Это связано с тем, что, во-первых, они занимают меньше места в памяти и, во-вторых, что более важно, не требуют дополни- тельных ссылок в память для выборки операндов. (Выборка операнда из регистра происходит намного быстрее, чем из опе- ративной памяти.) Точно так же, когда используется непосред- ственный операнд, то при обработке команды он уже присут- ствует как ее составная часть, и, следовательно, нет необходи- мости в его выборке откуда-либо еще. Использование косвенной адресации часто позволяет экономить команду (см. стро- ку 70). Вы можете заметить, что в то же время некоторые из- менения требуют внесения в программу дополнительных команд. Например, замена команды СОМР на COMPR в стро- ке 150 вынудила нас добавить команду CLEAR в строку 132, Тем не менее в целом скорость выполнения программы возрас- тет, так как команда CLEAR выполняется только один раз
2.2. Машинно-зависимые характеристики ассемблера 51 Строка Исходное предложение 5 COPY START & КОПИРОВАНИЕ ФАЙЛА 10 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА 12 LDB ♦LENGTH УСТАНОВКА БАЗОВОГО РЕГИСТРА 13 BASE LENGTH 15 CLQQF +JSUK RBREC ВВОД ВХОДНОЙ ЗАПИСИ 20 LDA LENGTH ПРОВЕРКА НА EOF (LENGTH = 0> 25 COMP ♦« 3® JEQ ENBFIL ВЫХОД, ЕСЛИ НАШЛИ EOF 35 +JSUB WRREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ 40 J CLOOP ЦИКЛ 45 ENDFIL LDA EOF ЗАНЕСЕНИЕ МАРКЁРА КОННА ФАЙЛА 50 .STA BUFFER 55 LDA ♦3 УСТАНОВИТЬ LENGTH = 3 60 STA LENGTH 65 +JSUB WRREC ЗАПИСЬ EOF 70 J ORETADR ВОЗВРАТ ИЗ ПРОГРАММЫ 80 EOF BYTE C'EOF' 95 RETADR RESH 1 100 LENGTH RESH 1 ДЛИНА ЗАПИСИ 105 BUFFER RESB 4096 ДЛИНА БУФЕРА - 4096 БАЙТ 110 115 • ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 125 RDREC CLEAR X. ОБНУЛЕНИЕ СЧЕТЧИКА ШЛА 130 CLEAR A ОБНУЛЕНИЕ РЕГИСТРА А 132 CLEAR S ОБНУЛЕНИЕ РЕГИСТРА S 133 +LDT ♦4096 135 FLOOR тп INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА 140 JEQ RLOOP НИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 150 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х'в» 155 JEft EXIT ВЫХОД ИЗ ЦИКЛА ПО концу ЗАПИСИ 160 STCH BUFFERrX ЗАПИСЬ СИМВОЛА В БУФЕР 165 TIXR T ЦИКЛ ДО ДОСТИХЕНИЯ 170 JLT RLOOP МАКСИМАЛЬНОЙ длины 175 EXIT STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ 180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 185 INPUT BYTE XW КОД УСТРОЙСТВА ВВОДА 195 M 200 К ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 210 WRREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 212 LDT LENGTH 215 «LOOP 7D OUTPUT ПРОВЕРКА УСТРОЙСТВА ВЫВОДА 220 JEQ ULOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 225 LDCH BUFFER,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 230 WD OUTPUT ВЫВОД СИМВОЛА 235 TIXR T ЦИКЛ, ПОКА НЕ БУДУТ 240 JLT WLOOP ВЫВЕДЕНЫ ВСЕ СИМВОЛЫ 245 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 250 OUTPUT BYTE X'05' КОД УСТРОЙСТВА ВЫВОДА 255 END FIRST Рис. 2.5. Пример программы на языке ассемблера УУМ/ДС.
52 Гл. 2. Ассемблеры для каждой читаемой записи, а команда COMPR (более быст- рая, чем СОМР) выполняется для каждого байта. В разд. 2.2.1 рассматривается процесс трансляции програм- мы для УУМ/ДС. При этом основное внимание обращено на те отличия ассемблера УУМ/ДС, которые продиктованы новы- ми способами адресации. (Вам следует взглянуть на форматы команд и способы вычисления целевого адреса, описанные в разд. 1.3.2.) Эти отличия являются прямым следствием расши- рения аппаратных возможностей. В разд. 2.2.2 обсуждаются отличия версии ассемблера УУМ/ДС, имеющие косвенную связь с новыми аппаратными возможностями. Например, расширенный объем оперативной памяти УУМ/ДС позволяет предположить, что мы будем иметь достаточно места для одновременной загрузки и исполнения не- скольких программ. Такое разделение машины между несколь- кими программами называется мультипрограммным режимом обработки. Этот режим позволяет более эффективно использо- вать аппаратуру ЭВМ. (Мы вернемся к этому вопросу в связи с операционными системами в гл. 6.) Однако для того, чтобы полностью использовать преимущества мультипрограммного ре- жима, мы должны иметь возможность загружать программу в любое свободное место оперативной памяти (а не в фиксиро- ванное место, определенное во время трансляции). При этом возникает необходимость перемещения программ в оперативной памяти. В разд. 2.2.2 мы обсудим эту проблему в ее связи с ассемблером. 2.2.1. Форматы команд и способы адресации В этом разделе мы рассмотрим трансляцию предложений исходной программы УУМ/ДС (рис. 2.5) в соответствующее им объектное представление (рис. 2.6), обращая особое внимание на обработку ассемблером различных командных форматов и способов адресации. Заметим, что предложение START задает теперь 0 в качестве начального адреса программы. Как будет сказано в следующем разделе, это является признаком переме- щаемой программы. Однако процедура трансляции команд остается при этом такой же, как если бы программа действи- тельно загружалась с адреса 0. Трансляция команд вида регистр — регистр, таких как CLE- AR (строка 125) или COMPR (строка 150), не представляет никаких новых проблем. Ассемблер должен просто преобразо- вать мнемонические коды операций в их машинное представле- ние (используя ОРТАВ) и заменить мнемонические имена ре- гистров на их числовые эквиваленты. Так же как и для команд других видов, это делается во время второго просмотра. Пре- образование имен регистров в их числовые значения может де-
2.2. Машинно-зависимые характеристики ассемблера 53 Объектный код] Строка Адрес Исходное предложение 5 0000 COPY START 0 10 0000 FIRST STL RETADR 172020 12 0003 LDB ♦LENGTH 69202D 13 BASE LENGTH 15 0006 CLOOP +JSUB RDREC 4B101036 20 000А LDA LENGTH 032026 25 000D COMP ♦0 ЛПЛААЛ iTOVVO 30 0010 JEQ ENDFIL 332007 35 0013 +JSUB WRREC 4B10105B 40 0017 J CLOOP 3F2FEC 45 001А ENDFIL LDA EOF 032010 50 0010 STA BUFFER 0F2016 55 0020 LDA ♦3 010003 60 0023 STA LENGTH 0F200D 65 0026 +JSUB WRREC 4B10105O 70 002А J GRETADR 3E2003 80 0020 EOF BYTE C'EOF' 454F46 95 0030 RETADR RESW 1 100 0033 LENGTH RESW 1 105 0036 BUFFER RESB 4096 110 115 « ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 M 125 1036 RDREC CLEAR X B410 130 1038 CLEAR A B400 132 103А CLEAR S B440 133 103С +LDT ♦4096 75101000 135 1040 RLOOP TD INPUT E32019 140 1043 JEQ RLOOP 332FFA 145 1046 RD INPUT DB2013 150 1049 COMPR A,S A004 155 1040 JEQ EXIT 332008 160 104Е STCH BUFFERS 57C003 165 1051 TIXR T B850 170 1053 JLT RLOOP 3B2FEA 175 1056 EXIT STX LENGTH 134000 180 1059 RSUB 4F0000 185 105С INPUT BYTE X'Fl' Fl 195 M 200 * ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ ВУФЕРА 205 210 1050 WRREC CLEAR X B410 212 105F LDT LENGTH 774000 215 1062 WLOOP TD OUTPUT £32011 220 1065 JEQ WLOOP 332FFA 225 1068 LDCH buffers 53C003 230 106В WD OUTPUT DF2008 235 106Е TIXR T B850 240 1070 JLT WLOOP 3B2FEF 245 1073 RSUB 4F0000 250 1076 OUTPUT BYTE X'05' 255 END FIRST Рис. 2.6. Объектный код для программы на рис, 2,5.
54 Гл. 2. Ассемблеры латься с помощью отдельной таблицы. Однако часто бывает удобно использовать для этих целей таблицу имен. Для этого в SYMTAB заранее заносятся имена регистров (А, Хит. д.) и их значения (0, 1 и т. д.). Для трансляции большинства команд вида память — регистр используется относительный способ адресации (либо относи- тельно счетчика команд, либо относительно базы). В любом случае ассемблер должен вычислить смещение, которое являет- ся составной частью объектной команды. Оно вычисляется так, чтобы после его сложения с регистром счетчика команд (PC)] или базовым регистром (В) получить требуемый целевой адрес. Конечно, величина полученного смещения должна быть не слишком большой, чтобы поместиться в 12-разрядном поле команды. Это означает, что величина смещения должна нахо- диться в диапазоне от 0 до 4095 (для адресации относительно базы) или в диапазоне от —2048 до +2047 (для адресации от- носительно счетчика команд). Если не подходит ни один из относительных способов адре- сации (из-за того что величина смещения слишком велика), следует использовать расширенный 4-байтовый командный фор- мат (формат 4). Этот формат имеет 20-разрядное адресное поле, позволяющее хранить полный адрес. В этом случае ни- какого смещения не вычисляется. Например, в команде 15 0006 CLOOP +JSUB RDREC 4В101036 адрес команды равен 1036. Это полный адрес, который хранит- ся в команде вместе с признаком, указывающим на использо- вание расширенного командного формата (разряд е установ- лен в 1). Отметим, что программист должен с помощью префикса + сам указать команды, в которых используется расширенный командный формат (см. строку 15). Если нельзя применить ни один из способов относительной адресации и нет указания об использовании расширенного формата, то правильная тран- сляция команды невозможна. В этом случае ассемблер должен выдать сообщение об ошибке. Рассмотрим теперь детально, как вычисляется смещение для относительных способов адресации. По существу, ассемблер должен в обратном порядке выполнить те операции, которые выполняются при вычислении целевого адреса. Вам следует вспомнить, как это делается, посмотрев еще раз разд. 1.3.2. Типичным примером команды, в которой используется адреса- ция относительно счетчика команд, является команда 10 0000 FIRST STL RETADR 17202D Во время выполнения команд в УУМ/ДС (как и в большин- стве ЭВМ) счетчик команд продвигается вперед после выборки
2.2. Машинно-зависимые характеристики ассемблера 55 очередной команды (но до ее выполнения). Таким образом, во время выполнения команды STL регистр PC будет содержать адрес следующей команды (в данном случае 0003). Из листин- га мы также видим, что метка RETADR (строка 95) имеет ад- рес 0030. (Ассемблер, естественно может получить эту инфор- мацию из SYMTAB.) Необходимое нам смещение вычисляется как 30 — 3 = 2D. Во время выполнения этой команды ее целе- вой адрес будет вычисляться как (PC) + disp, что и даст нуж- ный нам результат (0030). Обратите внимание, что разряд р установлен в 1, что является признаком адресации относитель- но счетчика команд. Таким образом, два последних байта ма- шинной команды содержат код 202. Тот факт, что в данной команде не используется ни косвенная адресация, ни непосред- ственное задание операнда, указывается установкой 1 в раз- ряды п и i. Поэтому первый байт машинной команды содержит код 17, а не 14 (см. описание значений разрядов-признаков на рис. 1.1 в разд. 1.3.2). Другим примером адресации относительно счетчика команд может служить предложение 40 0017 J CLOOP 3F2FEC Здесь адрес операнда равен 0006. Во время выполнения коман- ды регистр PC будет иметь значение 0001А. Величина смеще- ния вычисляется как 6 — 1А = —14. Так как это отрицательное число, то оно будет представлено в дополнительном коде. В ма- шинной команде для поля смещения отводится 12 разрядов. Поэтому смещение будет записано как FEC. Аналогичным образом вычисляется смещение для адресации относительно базового регистра. Основное отличие заключается в том, что ассемблеру известно значение счетчика команд на момент выполнения программы, в то время как значение базо- вого регистра определяется программистом. Поэтому програм- мист должен сообщить ассемблеру значение базового регистра на момент выполнения программы. В нашем примере это дела- ется с помощью директивы ассемблера BASE. Данное предло- жение (строка 13) сообщает ассемблеру, что базовый регистр будет содержать адрес метки LENGTH. Предшествующая команда LDB 4f=LENGTH загружает это значение в регистр во время работы программы. До тех пор, пока ассемблер не встретит другую команду BASE, он будет полагать, что значе- ние базового регистра не меняется. Возможно, что в последую- щих фрагментах программы будет желательно использовать ре- гистр В для других целей (например, для временного хранения некоторого значения). В этом случае программист должен ис- пользовать другую директиву ассемблера (например, NOBASE)' для того, чтобы сообщить ему, что содержимое базового реги- стра теперь нельзя использовать для базирования,
56 Гл. 2. Ассемблеры Важно уяснить, что BASE и NOBASE это директивы ассемб- лера, которые не переводятся в выполняемые машинные коман- ды. Для загрузки базового регистра требуемым значением про- граммист должен предусмотреть в программе соответствующие команды. Если этого не сделать, то целевой адрес будет вычи- сляться неверно. Типичным примером команды, в которой ис- пользуется адресация относительно базы, является команда 160 104Е STCH BUFFER,X 57С003 В соответствии с предложением BASE регистр В будет содер- жать во время исполнения программы значение 0030 (адрес LENGTH). Адрес метки BUFFER равен 0036. Таким образом, смещение определяется как 36 — 33 = 3. Заметим, что в машин- ном представлении команды оба разряда х и 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 103С +LDT #4096 75101000 В данном случае операнд (4096) слишком велик для 12-разряд- ного поля и поэтому используется расширенный командный формат. (Если бы операнд был велик и для 20-разрядного поля, то мы не могли бы задать его непосредственно в команде.)
2.2. Машинно-зависимые характеристики ассемблера 575 Другой способ использования непосредственного операнда показан в команде 12 0003 LDB #LENGTH 69202D В этом предложении непосредственный операнд задан именем LENGTH. Поскольку его значением является адрес, то в ре- зультате выполнения этой команды в регистр В будет загружен адрес, назначенный переменной LENGTH. Заметим, что в дан- ном случае используется комбинация адресации относительно счетчика команд с непосредственным заданием операнда. Хотя на первый взгляд такая адресация может выглядеть не совсем обычной, тем не менее она не противоречит предыдущим рас- суждениям. Дело в том, что в общем случае всегда вначале вычисляется целевой адрес, и если в команде установлен при- знак непосредственного операнда, то этот адрес (а не содержи- мое по этому адресу) будет использован в качестве операнда. (В предложении LDA в строке 55 разряды х, b и р установле- ны в 0. Таким образом, целевой адрес просто совпадает со смещением 003.) Ассемблирование команд, использующих косвенную адреса- цию, фактически не представляет ничего нового. Для того что- бы получить требуемый целевой адрес, вначале обычным обра- зом вычисляется смещение. Затем в разряд п генерируемой команды устанавливается признак косвенной адресации. При- мером использования адресации относительно счетчика команд в комбинации с косвенной адресацией является предложение в строке 70. 2.2.2. Перемещение программ Как было отмечено ранее, очень часто желательно иметь возможность одновременно выполнять несколько программ, раз- деляющих между собой оперативную память и другие ресурсы машины. Если бы нам заранее было точно известно, какие про- граммы будут выполняться одновременно, то мы могли бы во время ассемблирования назначить каждой программе подходя- щие адреса таким образом, чтобы не было ни взаимного пере- крытия программ, ни потери места в оперативной памяти. Од- нако чаще всего столь точное планирование выполнения про- грамм не пригодно с практической точки зрения. (Обычно мы не знаем абсолютно точно, когда задания начнут выпол- няться, сколько времени займет их выполнение и т. п.). Поэто- му хотелось бы иметь возможность загружать программу на любое место в оперативной памяти, где для нее имеется доста- точно пространства. В этом случае фактический начальный ад- рес программы не известен до момента загрузки
58 Гл. 2. Ассемблеры Программа, которую мы рассмотрели в разд. 2.1, представ- ляет собой пример абсолютной программы (или абсолютного ассемблирования). Для того чтобы она правильно выполнялась, ее необходимо загрузить с адреса 1000 (т. е. с адреса, назна- ченного при ассемблировании). Чтобы убедиться в этом, рас- смотрим команду 55 101В LDA THREE 00102D из программы, представленной на рис. 2.2. В объектной про- грамме (рис. 2.3) этому предложению соответствует команда 00102D, которая обеспечивает загрузку содержимого оператив- ной памяти по адресу 102D в регистр А. Предположим, что мы попытались загрузить и выполнить эту программу с адреса 2000 (вместо 1000). В этом случае адрес 102D не будет со- держать ожидаемого значения. Скорее всего данный адрес бу- дет принадлежать какой-либо другой пользовательской про- грамме. Очевидно, для того чтобы программа могла правильно рабо- тать, начиная с адреса 2000, мы должны сделать определенные изменения в адресной части данной команды. С другой сторо- ны, некоторые фрагменты программы (например, константа 3, сгенерированная в строке 85) должны остаться неизменными вне зависимости от начального адреса загрузки. Если рассмат- ривать объектный код отдельно, то в общем случае невозмож- но определить, какие значения представляют адреса, зависящие от месторасположения программы, а какие — неизменяемые объекты. Поскольку ассемблеру не известен фактический адрес нача- ла загрузки, он не может выполнить необходимую настройку адресов, используемых программой. Однако ассемблер может указать загрузчику те части объектной программы, которые нуждаются в настройке при загрузке. Объектная программа, содержащая информацию, необходимую для выполнения подоб- ной модификации, называется перемещаемой программой. Для того чтобы детально обсудить возникающие здесь проб- лемы, рассмотрим программу на рис. 2.5 и 2.6. В предыдущем разделе мы ассемблировали данную программу с нулевым на- чальным адресом. На рис. 2.7а показана ее загрузка с адре- са 0. Команда JSUB (строка 15) загружается, начиная с ад- реса 0006. Ее адресное поле содержит значение 01036, которое является адресом команды с меткой RDREC. (Это, конечно, те же самые адреса, которые были назначены ассемблером.) Предположим теперь, что мы хотим загрузить эту же про- грамму с адреса 5000, как показано на рис. 2.76. Теперь команда с меткой RDREC имеет адрес 6036. Таким образом, адресная часть команды JSUB должна быть модифицирована так, как показано на рисунке. Аналогично, если бы мы загрузи-
2.2. Машинно-зависимые характеристики ассемблера 59 ли программу с адреса 7420 (рис. 2.7в), то потребовалось бы приведение команды JSUB к виду 4В108456, чтобы ее адрес- ная часть соответствовала новому значению адреса RDREC. Обратите внимание, что независимо от того, куда загружа- ется программа, разница между адресом RDREC и началом программы составляет 1036 байт. Это означает, что мы можем оооо оооо 4В101036 1036 ♦—RDREQ 1076 В4Ю 5000 5006 6036 6076 (+JSUB новее) 4В106036 В4Ю 6 (+JSUB BDREC) 4-RPRHC 7420 7426 6456 3496 4В108456 (iJSUB RPRgC) В41О 4—RDREC В Рис. 2.7. Примеры перемещения программ. решить проблему перемещения программы следующим обра- зом: 1. Когда ассемблер генерирует объектный код команды JSUB, мы полагаем, что он заносит адрес метки RDREC отно- сительно начала программы. (Вот почему мы установили вна- чале счетчик адреса равным 0.) 2. Ассемблер, кроме того, вырабатывает команду для за- грузчика, которая предписывает ему прибавить к содержимому адресного поля команды JSUB начальный адрес программы. Естественно, что команда загрузчика должна быть составной частью объектной программы. Это можно сделать с помощью дополнительного типа записи объектного формата — записи-мо- дификатора. Эта запись имеет следующий формат;
60 Гл. 2. Ассемблеры Запись-модификатор: Столбец 1 Столбцы 2 — 7 Столбцы 8 — 9 М. Начальный адрес модифицируемого адрес- ного поля относительно начала программы (шестнадцатеричный). Длина модифицируемого адресного поля в полубайтах (шестнадцатеричная). Длина хранится в полубайтах (а не в байтах), так как моди- фицируемое адресное поле не обязательно содержит целое чис- ло байтов. (Например, адресное поле в рассмотренной выше команде JSUB занимает 20 разрядов, что составляет 5 полу- байтов.) Месторасположение модифицируемого адресного поля задается адресом байта, в котором содержатся старшие раз- ряды адресного поля. Если модифицируемое адресное поле за- нимает нечетное количество полубайтов, то предполагается, что оно начинается с середины байта, адрес которого задан в столбцах 2—7. Эти соглашения, естественно, тесно связаны с архитектурой УУМ/ДС. Для других типов машин представление длины ад- ресного поля в полубайтах может оказаться неудобным (см. упр. 2.2.7). Для команды JSUB, которую мы используем в качестве примера, запись-модификатор должна иметь вид М00000705 Она указывает на то, что начальный адрес программы необходи- мо добавить к полю, которое начинается с адреса 00007 (отно- сительно начала программы), и что длина адресного поля рав- на 5 полубайтам. Таким образом, в ассемблированной команде 4В101036 первые 12 разрядов (код 4В1) останутся неизменны- ми. Адрес начала загрузки программы будет добавлен к по- следним 20 разрядам (01036) для того, чтобы получить пра- вильный адрес операнда. (Вам следует самостоятельно убедить- ся в том, что это даст результат, показанный на рис. 2.7.) Точно такие же действия необходимо выполнить для пере- мещения команд, заданных в строках 35 и 65 (рис. 2.6). Остальные команды не требуют модификации при загрузке. В одних случаях потому, что операнды команд расположены не в оперативной памяти (например, CLEAR S или LDA #3). В других потому, что для задания операнда использована от- носительная адресация. Например, команда в строке 10 (STL RETADR) ассемблируется с помощью адресации относительно счетчика команд со смещением 02D. Неважно, с какого адреса будет фактически загружена программа. В любом случае сло- во, помеченное меткой RETADR, будет смещено на заданную величину (2D) относительно команды STL, Поэтому данную
2.2. Машинно-зависимые характеристики ассемблера команду модифицировать не нужно, так как во время ее вы- полнения счетчик команд будет содержать реальный адрес сле- дующей команды и, таким образом, процесс вычисления целе- вого адреса обеспечит получение правильного реального адре- са, соответствующего RETADR. Точно так же расстояние между LENGTH и BUFFER всегда будет равно трем байтам. Таким образом, после смещения команды в строке 160, использующей адресацию относительно базы, не будет требовать модификации при перемещении про- граммы. (Содержимое базового регистра будет, конечно, зави- сеть от месторасположения программы однако его значение устанавливается автоматически при выполнении команды LDB ^LENGTH, в которой используется адресация относительно счетчика команд.) HCOPY л000000д00107 7 ТЛ000000Л1ВЛ172020Л692020Л4В101036Л032026Л290000Л33 2007Л4В10105БЛЗР2ЕЕСЛ032010 TA00001DA13AOF2016A010003AOF200DA4B10105DA3E2003A454F46 Ta001036a1^B410aB400aB440a75101000aE32019a332FF^)B2013aA004a332008a57c003aB850 T0010531D3B2FEA1 340Q04FOOOOF1B4107 74000E3 201 1.3 32FFA5 3C003.DF2008.B850 Л Л Л А Л АЛЛ А Л Л. Л А tT.O01O7OO7 3.B2FEF.4F00OO.O5 А АЛ Л ' Л Ма000007а05 На000014а05 Ма000027а05 еаоооооо Рис. 2.8. Объектная программа, соответствующая рис. 2.6. Теперь нам абсолютно ясно, что модификация во время за- грузки требуется только в командах, использующих прямую (как альтернатива относительной) адресацию. Для программ УУМ/ДС прямая адресация используется только в расширен- ном (4-байтовом) командном формате. Это существенное пре- имущество относительной адресации. Если бы мы попытались переместить программу, приведенную на рис. 2.1, то увидели бы, что почти все команды требуют модификации. На рис. 2.8 показано объектное представление, соответству- ющее исходной программе на рис. 2.5. Обратите внимание, что записи тела программы остались такими же, как если бы они были подготовлены абсолютным ассемблером (с нулевым на- чальным адресом). Однако теперь адреса загрузки рассматри- ваются не как абсолютные, а как относительные величины, (То же самое, конечно, справедливо для адресов в записях-мо- дификаторах и записи-конце.) Для каждого адресного поля, требующего настройки при перемещении программы, имеется своя запись-модификатор (в нашем случае их 3 и они все для команд -j-JSUB). Вам следует самостоятельно проверить каж-
62 Гл. 2. Ассемблеры дую запись-модификатор и убедиться, что вы поняли, как она формируется. В гл. 3 мы детально рассмотрим, как загрузчик осуществляет требуемую модификацию программ. Сейчас важ- но, чтобы вы хорошо уяснили изложенные здесь концепции, так как они послужат основой для материала, обсуждаемого в сле- дующем разделе, 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.3. Машинно-независимые характеристики ассемблера 63 Строка Исходное предложение 5 COPY START 0 КОПИРОВАНИЕ ФАЙЛА 10 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА 13 LDB ♦LENGTH УСТАНОВКА БАЗОВОГО РЕГИСТРА 14 BASE LENGTH 15 CLOOP 4 JSUB RDREC ВВОД ВХОДНОЙ ЗАПИСИ 20 LDA LENGTH ПРОВЕРКА НА EOF (LENGTH = 0) 25 COMP ♦0 за JEQ ENDFIL ВЫХОД, ЕСЛИ НАШЛИ EOF 35 4 JSUB WRREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ 40 J CLOOP ЦИКЛ 45 ENDFIL LDA -C'EOF' ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА 50 STA BUFFER 55 LDA ♦3 УСТАНОВИТЬ LENGTH = 3 60 STA LENGTH 65 4JSUB WRREC ЗАПИСЬ EOF 70 J ©RETADR ВОЗВРАТ ИЗ ПРОГРАММЫ 93 LTORG 95 RETADR RESW 1 100 LENGTH RESW 1 ДЛИНА ЗАПИСИ 105 BUFFER RESB 4096 ДЛИНА БУФЕРА - 4096 БАЙТ 106 BUFEND EQU * 107 MAXLEN EQU BUFEND-BUFFER МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ 110 M 115 JT ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР 120 125 RDREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 130 CLEAR A ОБНУЛЕНИЕ РЕГИСТРА А 132 CLEAR S ОБНУЛЕНИЕ РЕГИСТРА S 133 4LDT ♦MAXLEN 135 RLOOP TD INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА 140 JEQ RLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ готовности 145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 150 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х'00') 155 JEQ EXIT ВЫХОД ИЗ ЦИКЛА ПО КОНЦУ ЗАПИСИ 160 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 165 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ 170 JLT RLOOP МАКСИМАЛЬНОЙ ДЛИНЫ 175 EXIF STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ 180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 185 INPUT BYTE X'Fl' КОД УСТРОЙСТВА ВВОДА 195 200 » ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 a 210 WRREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 212 LDT LENGTH 215 WLOOP TD ~X'05' ПРОВЕРКА УСТРОЙСТВА ВЫВОДА 220 JEQ WLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 225 LDCH BUFFER, X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 230 WD =X'05Z ВЫВОД СИМВОЛА 235 TIXR T ЦИКЛ, ПОКА НЕ БУДУТ 240 JLT WLOOP ВЫВЕДЕНЫ ВСЕ СИМВОЛЫ 245 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 255 END FIRST Рис. 2.9. Программа, демонстрирующая дополнительные возможности ао^ сем олер а.
Строка Адрес Исходное предложение Об^ектнмй код 5 0000 COPY START 0 10 0000 FIRST STL RETADR 17202D 13 0003 LDB ♦LENGTH 69202D 14 BASE LENGTH 15 0006 CLOOP +JSUB RDREC 4B101036 20 000А LDA LENGTH 032026 25 , 000D COMP ♦0 290000 30 0010 JEQ ENDFIL 332007 35 0013 +JSUB WRREC 4B10105D 40 0017 J CLOOP 3F2FEC 45 001А ENDFIL LDA -C'EOF* 032010 50 001П STA BUFFER 0F2016 55 0020 LDA ♦3 010003 60 0023 STA LENGTH 0F200D 65 0026 +JSUB WRREC 4B101051 70 002А J GRETADR 3E2003 93 LTORG 002ft м =C'EOF 454F46 95 0030 RETADR RESW 1 100 0033 LENGTH RESW 1 105 0036 BUFFER RESB 4096 106 1036 BUFEND EQU * 107 1000 MAXLEN EQU BUFENp-BUFFER 110 115 * ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 125 1036 RDREC CLEAR X B410 130 1038 CLEAR A B400 132 103А CLEAR S B440 133 103С +LDT ♦MAXLEN 75101000 135 1040 RLOOP TD INPUT E32019 140 1043 JEQ RLOOP 332FFA 145 1046 RD INPUT DB2013 150 1049 COMPR A,S A004 155 104В JEQ EXIT 332008 160 104Е STCH BUFFER,X 57C003 165 1051 TIXR T B850 170 1053 JLT RLOOP 3B2FEA 175 1056 EXIT STX LENGTH 134000 180 1059 RSUB 4F0000 185 105С INPUT BYTE X'Fl' Fl 195 200 « ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 210 105D WRREC CLEAR X B410 212 105F LDT LENGTH 774000 215 1062 WLOOP TD ==X'05' E32011 220 1065 JEQ WLOOP 332FFA 225 1068 LDCH BUFFERrX 53C003 230 106В WD *=X'05' DF2008 235 106Е TIXR T B850 240 1070 JLT WLOOP 3B2FEF 245 1073 RSUB 4F0000 255 END FIRST 1076 * *X'05' 05 Рис» 2,10. Объектный код для программы на рис. 2.9,
2.3. Машинно-независимые характеристики ассемблера 65 представленной на рис. 2.5; другие изменения мы обсудим в разд. 2.3.) В нашем языке ассемблера литералы задаются с помощью префикса =, за которым следует константа, задаваемая точно так же, как в предложении BYTE. Таким образом, в предло- жении 45 001А ENDFIL LDA =C'EOF' 032010 литерал определяет 3-байтовый операнд, значением которого является строка символов EOF. Аналогично в предложении 215 1062 WLOOP TD =Х'О5' Е32011 задается 1-байтовый литерал, имеющий шестнадцатеричное значение 05. Символьная нотация для задания литералов мо- жет быть различной, однако в большинстве ассемблеров для того, чтобы упростить идентификацию литералов, используется определенный символ (у нас =). Важно уяснить различие между литералом и непосредствен- ным операндом. В случае непосредственного операнда его зна- чение транслируется как составная часть машинной команды. В случае литерала его значение генерируется в виде константы в оперативной памяти, а ее адрес используется в качестве це- левого адреса команды. Употребление литерала дает точно та- кой же результат, как если бы программист явно определил константу и использовал в качестве операнда ее метку. (Дейст- вительно, сгенерированный объектный код для строк 45 и 215 на рис. 2.10 идентичен объектному коду для соответствующих строк программы на рис. 2.6.) Вам следует сравнить объект- ный код, сгенерированный для строк 45 и 55 (рис. 2.10), для того, чтобы убедиться, что вы уяснили разницу в обработке ли- тералов и непосредственных операндов. Все литеральные операнды, используемые в программе, объ- единяются в один или несколько литеральных пулов (literals pools). Обычно литеральный пул помещается в конце програм- мы и распечатывается в ее листинге. В литеральном пуле для каждого литерала показаны назначенный ему адрес и сгене- рированное значение. Пример такого литерального пула при- веден на рис. 2.10 (он расположен непосредственно после пред- ложения END). В данном случае пул состоит только из од- ного литерала =Х'О5'. Однако в некоторых случаях хотелось бы размещать лите- ральный пул в другом месте объектной программы. Это можно сделать с помощью директивы ассемблера LTORG (строка 93 на рис. 2.9). Когда ассемблер встречает предложение LTORG, он создает литеральный пул, содержащий все литеральные опе- ранды, которые были использованы в программе с момента обработки предыдущего предложения LTORG (или с начала 3 Зак. 792
65 Гл. 2. Ассемблеры программы). Литеральный пул помещается в объектную про- грамму в то место, где было встречено предложение LTORG (см. рис. 2.10). Конечно, литералы, помещенные в литеральный пул по команде LTORG, не будут повторно помещаться в лите- ральный пул в конце программы. Если бы мы не использовали предложение LTORG в строке 93, то литерал =C/EOF' был бы помещен в литеральный пул в конце программы. В этом случае данный операнд получил бы адрес 1073 и мы не смогли бы использовать для него адреса- цию относительно счетчика команд. Конечно, все дело в том, что мы зарезервировали очень большое пространство для мас- сива BUFFER. Расположив литеральный пул перед этим мас- сивом, мы смогли избежать использования расширенного командного формата для ссылок на литералы. Обычно необхо- димость в команде, подобной LTORG, возникает тогда, когда желательно расположить литеральные операнды в непосредст- венной близости от команд, в которых они используются. Большинство ассемблеров умеют распознавать повторное ис- пользование литералов и хранят только один экземпляр каж- дого из них. Например, литерал =Х'О5' используется в нашей программе в строках 215 и 230. Однако для его хранения гене- рируется только одна константа и операнды обеих команд ссы- лаются на адрес этой константы. Простейший способ распознать повторное использование литерала заключается в сравнении определяющих их строк сим- волов (в нашем случае это строка — Х'05'). Однако в некото- рых случаях лучше сравнивать не определения, а сгенерирован- ные коды. Например, литералы =C'EOF' и =X'454F46' опре- деляют идентичные значения. Если бы ассемблер умел распознавать их эквивалентность, то он мог бы не заводить дублирующую константу. В то же время выигрыш от подобного способа обработки литералов обычно не столь велик, чтобы оправдать дополнительные усложнения ассемблера. Если для распознавания повторного использования литера- лов используется символьная строка, то следует быть внима- тельными при обработке литералов, значение которых зависит от месторасположения в программе. Предположим, что мы раз- решаем использовать литералы, которые ссылаются на теку- щее значение счетчика размещений (часто обозначаются с по- мощью символа *). Такие литералы иногда бывают полезны для загрузки базового регистра. Например, предложения LDB =* BASE * расположенные в самом начале программы, обеспечивают за- грузку в регистр В начального адреса программы. В дальней-:
2 3 Машинно-независимые харакгеристики ассемблера 67 тем это значение можно использовать для адресации относи- тельно базы. Однако такое обозначение может вызвать трудности распо- знавания повторно используемых литералов. Если литерал =* появился бы в нашей программе в строке 13, то его значение было бы 0003. Если тот же операнд был бы задан в строке 55, то он определял бы операнд, имеющий значение 0020. В этом случае литеральные операнды имеют идентичные имена, но различные значения. Следовательно, оба эти операнда должны быть помещены в литеральный пул. Те же самые проблемы возникают, если литерал ссылается на какой-либо другой объ- ект, значение которого изменяется между двумя точками про- граммы. Теперь мы готовы к тому, чтобы описать, как ассемблер обрабатывает литеральные операнды. Основной структурой дан- ных, используемой при обработке литералов, является таблица литералов LITTAB. Для каждого использованного в программе литерала эта таблица содержит следующую информацию: имя литерала, его значение, длину и назначенный ему адрес. Очень часто LITTAB организуется в виде хеш-таблицы, в которой в качестве ключа используется имя литерала или его значение. Если во время первого просмотра ассемблер встречает лите-* ральный операнд, то он пытается найти его в LITTAB. Если литерал уже присутствует в таблице, то никаких дополнитель- ных действий не требуется. Если литерала в таблице еще нет, то он помещается в LITTAB (значение адреса пока остается неопределенным). Когда во время первого просмотра встреча- ется предложение LTORG или конец программы, ассемблер просматривает таблицу литералов и назначает каждому лите- ралу его адрес (за исключением тех литералов, которым адре- са были назначены ранее). После назначения адреса для оче- редного литерала счетчик размещений продвигается на длину литерального операнда. Во время второго просмотра адреса литеральных операндов извлекаются из LITTAB и используются для генерации объект- ного кода. Значения литеральных операндов в литеральном пуле заносятся в соответствующие места объектной программы точно так же, как если бы эти значения были сгенерированы с помощью предложений BYTE или WORD. Если значение ли- теральнего операнда представляет собой программный адрес (например, значение счетчика размещений), то ассемблер дол- жен сгенерировать для него соответствующую запись-модифи- катор в объектном представлении. Для того чтобы убедиться, что вы поняли, как создается и используется таблица литералов, вам следует применить з*
68 Гл. 2. Ассемблеры описанную нами процедуру к исходной программе на рис. 2.9. Объектный код и литеральные пулы, которые вы получите, должны быть такими, как показано на рис. 2.10. 2.3.2. Средства определения имен До сих пор в рассмотренных нами ассемблерных програм-. мах мы имели дело только с одним способом определения сим- волических имен пользователя. Эти имена появлялись в каче- стве меток команд или областей данных. Значением такой мет- ки является адрес, назначаемый предложению, в котором она определена. Большинство ассемблеров предоставляет програм- мисту дополнительные средства для определения имен и зада- ния их значений. К числу обычно используемых директив ас- семблера относится директива EQU (от EQUate). В общем виде это предложение записывается следующим образом: имя EQU значение Данное предложение определяет некоторое имя (т. е. вносит его в SYMTAB) и присваивает ему значение. Значение может задаваться в виде константы или выражения, в котором могут использоваться константы и ранее определенные, имена. Обра- зование и использование выражений мы обсудим в следующем разделе. Одним из общих применений EQU является введение сим- волических имен вместо числовых значений, чтобы упростить чтение программы. Например, в строке 133 программы на рис. 2.5 для того, чтобы загрузить в регистр Т число 4096, мы использовали предложение + LDT #4096 Данная величина представляет собой максимальную длину за- писи, которую мы можем прочитать с помощью подпрограммы RDREC. Однако смысл этого предложения далеко не очевиден. Если мы включим в нашу программу предложение MAXLEN EQU 4096 то сможем записать строку 133 как + LDT # MAXLEN Когда ассемблер встретит предложение EQU, он занесет MAX- LEN в таблицу имен (со значением 4096). Во время транс- ляции команды LDT ассемблер найдет MAXLEN в SYMTAB и использует его значение в качестве операнда. Получаемый при этом объектный код будет таким же, как и в предыдущей версии, однако исходная программа легче для понимания. Кро<
2.3. Машинно-независимые характеристики ассемблера 69 ме того, если понадобится изменить значение величины, задаю- щей максимальную длину записи, то проще сделать это в од- ном предложении EQU, чем искать все вхождения 4096 в ис- ходной программе. Другое общее применение EQU заключается в определении мнемонических имен для регистров. Мы предположили, что наш ассемблер распознает стандартные мнемонические имена регистров — А, X, L и т. д. Однако представим себе, что ас- семблер требует использовать для задания регистров их номе- ра (например, в команде RMO). Тогда мы должны были бы писать RMO 0, 1 вместо RMO А, X. В этом случае програм- мист мог бы включить в свою программу следующую последо- вательность предложений: A EQU 0 X EQU 1 L EQU 2 В результате обработки этих предложений имена А, X, L, ... были бы занесены в SYMTAB и имели бы значения 0, 1, 2.....После этого команда, подобная RMO А, X, была бы до- пустима. Ассемблер, обрабатывая такое предложение, должен был бы просмотреть SYMTAB и при трансляции использовать для имен А и X соответствующие им значения 0 и 1. На машине, подобной УУМ, задание для регистров пользо- вательских имен большого смысла не имеет. Гораздо проще использовать символические имена, встроенные в ассемблер. К тому же стандартные имена (base, index и т. п.) отражают способ использования регистров. Рассмотрим, однако, машину, имеющую регистры общего назначения (например, System/370). Обычно для обозначения этих регистров используются номера вида 0, 1, 2, ... или имена вида RO, Rl, R2, .... В конкретной программе некоторые из них могут быть использованы как ба- зовые регистры, другие — как индексные, третьи — как сумма- торы и т. д. Более того, в различных программах функциональ- ное распределение регистров может быть различным. В этом случае программист может определить имена, отражающие функциональное назначение регистров конкретной программы, с помощью следующих предложений: BASE EQU 1 COUNT EQU 2 INDEX EQU 3 Имеется еще одна общеупотребительная директива ассемб- лера, позволяющая косвенно задавать значения символическим
70 Гл. 2. Ассемблеры именам. Обычно эта директива называется ORG (от ORiGin). Она имеет следующий вид: ORG значение где значение представляет собой константу или выражение, в котором могут быть использованы константы и ранее опреде- ленные имена. Если во время ассемблирования программы встречается данное предложение, то заданное им значение за- носится в счетчик размещений (LOCCTR). Поскольку для определения значений имен используется LOCCTR, то предло- жение ORG распространяет свое влияние на все последующие определения меток. Конечно, ввиду того что счетчик размещений используется для управления распределением памяти в объектной программе, его изменение чаще всего приводит к ошибкам ассемблирова- ния. Однако иногда предложение ORG может быть полезно. Предположим, что мы хотим определить таблицу, имеющую следующую структуру: SYMBOL VALUE FLAGS STAB ----------------‘I---------------- 100 элементов ._______________ В этой таблице SYMBOL представляет собой 6-байтовое поле для хранения имен; VALUE—1-словное поле для хранения значения, присвоенного имени; FLAGS — 2-байтовое поле для спецификации типа и другой информации. Мы могли бы зарезервировать пространство для этой табли- цы с помощью предложения STAB RE SB 1100 Естественно, что мы хотим иметь возможность ссылаться на элементы таблицы с помощью индексной адресации (помещая в индексный регистр величину смещения требуемого элемента относительно начала таблицы). Так как нам нужен доступ к отдельным полям элементов, то мы должны определить метки SYMBOL, VALUE и FLAGS. С помощью предложений EQU, это можно сделать следующим образом: SYMBOL EQU STAB VALUE EQU STAB + 6 FLAGS EQU STAB + 9
2.3. Машинно-независимые характеристики ассемблера 71 Теперь для ссылки на поле VALUE мы можем использовать предложения вида LDA VALUE,X Однако такой способ определения лишь задает метки, но не дает наглядного представления о структуре таблицы. С помощью предложения ORG мы можем определить те же имена следующим образом: STAB RESB ORG 1100 STAB SYMBOL RESB 6 VALUE RESW 1 FLAGS RESB ORG 2 STAB + 1100 Первое предложение ORG заносит в счетчик размещений на- чальный адрес таблицы STAB. В следующем предложении мет- ке SYMBOL будет присвоено текущее значение LOCCTR (т. е. тот же адрес, что и STAB). Затем LOCCTR будет продвинут и его новое значение (STAB + 6) будет присвоено метке VA- LUE. Аналогично будет определено значение метки 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
72 Гл. 2. Ассемблеры недопустима. Причина этого ограничения кроется в самом про- цессе распознавания имен. Так, во втором случае мы не смо- жем присвоить значение метке ВЕТА (так как метка ALPHA еще не определена), а алгоритм работы нашего двухпросмотро- вого ассемблера требует, чтооы все имена были определены во время первого просмотра. Сходные ограничения применяются и для предложения ORG? все имена, используемые для задания нового значения счетчи- ка размещений, должны быть предварительно определены. Так, например, последовательность ORG ALPHA В7ТЕ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.3. Машинно-независимые характеристики ассемблера 73 Обычно допускаются арифметические выражения, которые строятся по стандартным правилам с помощью операций +, —, «, /. Деление чаще всего определяется как целочисленное. В выражениях можно использовать константы, метки и специ- альные термы. Одним из таких общеупотребительных специаль- ных термов является терм, ссылающийся на текущую величину счетчика размещений (часто обозначается как *). Значением этого терма является адрес, который будет присвоен очеред- ной команде или области данных. Так, на рис. 2.9 предложение 106 BUFEND EQU * устанавливает в качестве значения BUFEND адрес байта, рас- положенного непосредственно после поля, отведенного под бу- фер. В разд. 2.2 мы обсуждали проблемы, связанные с переме- щением программ. Мы видели, что в объектной программе име- ются адреса двух типов: относительные (их значения могут быть вычислены суммированием начального адреса програм- мы с некоторым постоянным смещением) и абсолютные (не за- висящие от начального адреса программы). Аналогично могут быть относительными или абсолютными термы и выражения. Константа — абсолютный терм. Метки команд и областей дан- ных, а также ссылка на текущее значение счетчика размеще- ний — относительные термы. Имена, определенные в предложе- ниях EQU (или других подобных директивах ассемблера), могут быть как абсолютными, так и относительными в зависимости от выражений, определяющих их значения. Выражения классифицируются как абсолютные или отно- сительные в зависимости от того, к какому типу относится его значение. Естественно, что выражение, состоящее только из аб- солютных термов, является абсолютным выражением. Однако абсолютное выражение может содержать и относительные тер- мы. Для этого необходимо, чтобы относительные термы обра- зовывали пары, причем в каждую пару должен входить как терм со знаком плюс, так и терм со знаком минус. Совершен- но необязательно, чтобы парные термы располагались друг за другом. Единственное требование — иметь возможность так пре- образовать выражение, чтобы все термы могли быть сгруппи- рованы в пары, в каждую из которых входит как положитель- ный, так и отрицательный терм. Относительные термы не могут быть использованы в операциях умножения и деления. Относительное выражение — это выражение, в котором все относительные термы, кроме одного, могут быть объединены в пары, как это было описано выше, причем терм, не вошедший ни в одну из пар, должен быть положительным. Как и в пер- вом случае, относительные термы нельзя использовать в опе- рациях умножения и деления. Выражения, которые . не
74 Гл. 2. Ассемблеры удовлетворяют ни условиям абсолютного выражения, ни усло- виям относительного выражения, отмечаются ассемблером как ошибочные. Хотя приведенные выше правила могут показаться произ- вольными, они на самом деле имеют глубокий смысл. К числу выражений, удовлетворяющих данным определениям, относятся только те выражения, значения которых не теряюг смысла при перемещении программ. Относительный терм или выражение представляет собой некоторое значение, которое может быть записано в виде (S + г), где S — начальный адрес программы, а г — величина смещения относительно начального адреса. По- этому обычно относительный терм задает некоторую точку вну- три программы. Когда относительные термы комбинируются в пары, каждая из которых включает термы с противоположны- ми знаками, величины S, определяющие начальный адрес про- граммы, взаимно исключаются. В результате получается абсо- лютное значение. Рассмотрим, например, программу на рис. 2.9. В выраже- нии 107 MAXLEN EQU BUFEND - BUFFER обе метки BUFEND и BUFFER — относительные термы, каж- дый из которых определяет внутренний адрес программы. Од- нако само выражение (разность между двумя адресами) явля- ется величиной абсолютной и определяет длину в байтах обла- сти данных, отведенной под буфер. Значения таких выражений, как BUFEND + BUFFER, 100 — BUFFER или 3* BUFFER, не представляют ни абсо- лютную величину, ни внутренний адрес программы. Зависи- мость этих значений от адреса начальной загрузки такова, что она не имеет связи с чем-либо в самой программе. Поскольку маловероятно, чтобы от таких выражений была какая-либо польза, они рассматриваются как ошибочные. Для того чтобы определить тип выражения, мы должны иметь информацию о типе всех имен, определенных в програм- ме. Эта информация хранится в таблице имен в виде при- знака, определяющего тип значения (абсолютное или относи- тельное). Так, для программы на рис. 2.10 некоторые из эле- ментов таблицы имен могли бы иметь вид ИМЯ ТИП' ЗНАЧЕНИЕ RETADR R 0030 BUFFER R 0036 BUFEND R 1036 MAXLEN A 1000
2.3. Машинно-независимые характеристики ассемблера 75 С помощью этой информации ассемблер может легко опреде- лить тип каждого выражения и сгенерировать в объектной программе записи-модификаторы для относительных выра- жений. В разд. 2.3.5 мы рассмотрим программы, состоящие из не- скольких частей, каждая из которых может перемещаться не- зависимо от других. Как мы увидим, в этом случае должны быть модифицированы правила для определения типа выра- жения. 2.3.4. Программные блоки Во всех примерах, которые мы видели до сих пор, ассемб- лируемая программа обрабатывалась как единое целое. Хотя логически исходная программа содержит подпрограммы, обла- сти данных и т. п., она рассматривалась ассемблером как не- делимый объект, который переводился в единственный блок объектного кода. Внутри этой объектной программы сгенериро- ванные машинные команды и данные располагались в том же порядке, в котором они были написаны в исходной программе. Многие ассемблеры предоставляют более гибкие средства обработки исходной программы и соответствующего ей объект- ного кода. Одни из этих средств позволяют располагать сгене- рированные машинные команды и данные в объектной програм- ме в порядке, отличном от порядка, в котором расположены соответствующие им исходные предложения. Другие позволяют создавать несколько независимых частей объектной программы. Каждая из этих частей сохраняет свою индивидуальность и об- рабатывается загрузчиком отдельно от других частей. Мы ис- пользуем термин программные блоки (program blocks) для обо- значения сегментов, расположенных в пределах одного объект- ного модуля, и термин управляющие секции (control sections)' для обозначения сегментов, которые транслируются в независи- мые объектные модули. (Данная терминология, к сожалению, не является общепризнанной. Фактически в некоторых системах одни и те же средства языка ассемблера используются для обеспечения обеих этих логически различных функций.) В этом разделе мы рассмотрим как используются и обрабатываются ассемблером программные блоки. Разд. 2.3.5 посвящен обсуж- дению управляющих секций. На рис. 2.11 показано, как с помощью программных блоков может быть записана наша программа. В данном случае ис- пользуются три блока. Первый (непоименованный) программ- ный блок содержит выполняемые команды. Второй (с именем CDATA) содержит все области данных, длина которых не пре- вышает нескольких слов. Третий (с именем CBLKS) состоит из областей данных, занимающих значительный объем
56 Гл. 2. Ассемблеры Строка Исходное предложениог 5 СОРУ START 0 КОПИРОВАНИЕ ФАЙЛА 10- FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА 15 CLOOP JSUB RDREC ВВОД ВХОДНОЙ ЗАПИСИ 20 LDA LENGTH ПРОВЕРКА НА EOF (LENGTH * 0> 25 COMP ♦0 30 JEQ ENDFIL ВЫХОД, ЕСЛИ НАШЛИ EOF 35 JSUB URREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ 40 J CLOOP ЦИКЛ 45 ENDFXL LDA* МУEOF' ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА 50 STA BUFFER 55 LDA 03 УСТАНОВИТЬ LENGTH = 3 60 STA LENGTH 65 JSUB URREC ЗАПИСЬ EOF 70 J QRETADR ВОЗВРАТ ИЗ ПРОГРАММЫ 92 USE CDATA 95 RETADR RESU 1 100 LENGTH RESU 1 ДЛИНА ЗАПИСИ 103 USE CBLKS 105 BUFFER RESB 4096 ДЛИНА БУФЕРА - 4096 БАЙТ 106 BUFEND EQU * АДРЕС ПЕРВОГО БАЙТА ПОСЛЕ БУФЕРА 107 MAXLEN EQU BUFEND-BUFFER МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ 110 115 ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР 120 • 123 USE 125 RDREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 130 CLEAR A ОБНУЛЕНИЕ РЕГИСТРА А 132 CLEAR S ОБНУЛЕНИЕ РЕГИСТРА S 133 +LDT ♦MAXLEN 135 RLOOP TD INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА 140 JEQ RLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 150 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х'00'> 155 JEQ EXIT ВЫХОД ИЗ ЦИКЛА ПО КОНЦУ ЗАПИСИ 160 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 165 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ 170 JLT RLOOP МАКСИМАЛЬНОЙ ДЛИНЫ 175 EXIT STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ 180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 183 USE CDATA 185 INPUT BYTE X'Fl' КОД УСТРОЙСТВА ВВОДА 195 200 * ПОДПРОГРАММА .ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 № 208 USE 210 URREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 212 LDT LENGTH 215 ULOOP TD =X'05' ПРОВЕРКА УСТРОЙСТВА ВЫВОДА 220 JEQ ULOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 225 LDCH BUFFER,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 230 UD s=X'05' ВЫВОД СИМВОЛА 235 TIXR T ЦИКЛ, ПОКА НЕ БУДУТ 240 XT ULOOP ВЫВЕДЕНЫ ВСЕ СИМВОЛЫ 245 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ ,252 USE CDATA 253 LTORG 255 END FIRST Рис. 2.11, Пример программы с несколькими программными блоками.
2.3. Машинно-независимые характеристики ассемблера 77 оперативной памяти. Некоторые из возможных доводов в поль- зу подобного разделения обсуждаются в данном разделе. Директива ассемблера USE указывает на то, какие части исходной программы принадлежат к тем или иным блокам. Вначале предполагается, что все предложения исходной про- граммы являются частью непоименованного (задаваемого по умолчанию) блока. Если не использовано ни одного предложе- ния USE, то считается, что вся программа принадлежит одно- му этому блоку. Предложение USE в строке 92 сигнализирует о начале блока с именем CDATA. К этому блоку относятся все последующие предложения исходной программы вплоть до следующего предложения USE в строке 103, которое начинает блок с именем CBLKS. Предложение USE может также ука- зывать на продолжение ранее начатого блока. Так, предложе- ние в строке 123 продолжает непоименованный блок, а пред- ложение в строке 183 — блок CDATA. Как вы видите, каждый блок фактически может содержать несколько отдельных сегментов исходной программы. Ассемб- лер перегруппирует (логически) эти сегменты так, чтобы со- брать вместе отдельные части каждого блока. Затем каждому из этих блоков будут назначены адреса в объектной програм- ме в порядке появления блоков в исходной программе. Резуль- тат будет в точности таким же, как если бы программист соб- ственноручно перегруппировал предложения исходной програм- мы, собрав вместе предложения, принадлежащие каждому из блоков. Такая логическая перегруппировка осуществляется ассемб- лером во время первого просмотра. Для этого с каждым про- граммным блоком связывается свой счетчик размещений. При объявлении нового блока связанный с ним счетчик размещений устанавливается в 0. Текущее значение счетчика размещений сохраняется при переключении на другой блок и восстанавли- вается, когда возобновляется ранее определенный блок. Таким образом, во время первого просмотра каждая метка програм-: мы получает адрес относительно начала содержащего ее блока. Когда метка заносится в таблицу имен, то вместе с присвоен- ным ей адресом заносится имя или номер блока, которому она принадлежит. По завершению первого просмотра значение счетчика размещений каждого блока указывает на длину этого блока. Затем ассемблер может назначить каждому блоку на- чальный адрес в объектной программе (начиная с относитель- ного адреса 0). Во время второго просмотра для генерации объектного кода ассемблеру для каждого имени нужно знать значение адреса относительно начала объектной программы (а не относитель- но начала блока). Эту информацию можно легко получить из^
78 Гл. 2. Ассемблеры Строка Адрес Исходное предложение Объектннй код 5 0000 0 COPY START 0 10 0000 0 FIRST STL. RETADR 172063 15 0003 0 CLOOP JSUB RDREC 4£2021 20 0006 0 LDA LENGTH 032060 25 0009 0 COMP 00 290000 30 000С 0 JEQ ENDFIL 332004 35 000F 0 JSUB WRREC 4B203B 40 0012 0 J CLOOP 3F2FEE 45 0015 0 ENDFIL LDA =C'EOF' 032055 50 0018 0 STA BUFFER 0F2056 55 001В 0 LDA ♦3 010003 001Е 0 STA LENGTH 0F2048 65 0021 0 JSUB WRREC 4B2029 70 0024 0 J ORETADR 3E203F 92 0000 1 USE CDATA 95 0000 1 RETADR RESW 1 100 0003 1 LENGTH RESW 1 103 0000 2 USE CBLKS 105 0000 2 BUFFER RESB 4096 106 1000 2 BUFEND EQU * 107 1000 MAXLEN EQU BUFEND-BUFFER 110 ж 115 * ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 123 0027 0 USE 125 0027 0 RDREC CLEAR X B410 130 0029 0 CLEAR A B400 132 002В 0 CLEAR S B440 133 002D 0 +LDT ♦MAXLEN 75101000 135 0031 0 RLOOP TD INPUT E32038 140 0034 0 JEQ RLOOP 332FFA 145 0037 0 RD INPUT DB2032 150 003А 0 COMPR A,S A004 155 0030 0 JEQ EXIT 332008 160 003F 0 STCH BUFFER,X 57A02F 165 0042 0 TIXR T B850 170 0044 0 JLT RLOOP 3B2FSA 175 0047 0 EXIT STX LENGTH 13201F 180 004А 0 RSUB 4F0000 183 0006 1 USE CDATA 185 0006 1 INPUT BYTE X'Fl' Fl 195 >0 * ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ ВУФЕРА 205 w 208 0040 0 USE 210 004D 0 WRREC CLEAR X B410 212 004F 0 LDT LENGTH 772017 215 0052 0 WLOOP TD J=XZ05Z E3201B 220 0055 0 JEQ WLOOP 332FFA 225 0058 0 LDCH BUFFERS 53A016 230 005В 0 WD s=X,05/ DF2012 235 005Е 0 TIXR T B850 240 0060 0 JLT WLOOP 3B2FEF 245 0063 0 RSUB 4F0000 252 0007 1 USE CDATA 253 LTORG 0007 1 * ==C'EOF 454F46 000А 1 * *X'05' 05 255 END FIRST Рис. 2.12. Объектный КОД для программы на рис 2.11д
2.3. Машинно-независимые характеристики ассемблера 79 SYMTAB: достаточно просто прибавить к адресу метки началь- ный адрес ее блока. На рис. 2.12 показано, как этот процесс применяется к на- шей модельной программе. В столбце «Адрес» приведены от- носительный адрес (внутри блока) каждого предложения и но- мер соответствующего ему блока (0 = непоименованный блок, 1 = CDATA, 2 = CBLKS). По существу это та же информация, которая хранится в SYMTAB для каждого имени. Обратите внимание, что для значения MAXLEN (строка 107) номер бло- ка не указан. Это означает, что MAXLEN является абсолютной меткой, значение которой не связано ни с одним из блоков. В конце первого просмотра ассемблер создает рабочую таб- лицу, которая содержит начальные адреса и длины всех бло- ков. Для нашей модельной программы таблица выглядит так: Имя блока Номер блока Адрес Длина (непоименованный) 0 0000 0066 CDATA 1 0066 000В 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 для того, .чтобы быть уверенными, что литералы будут расположены
$0 Гл. 2. Ассемблеры впереди всех областей данных, занимающих значительный объем памяти. Конечно, использование программных блоков не дает ничего такого, что мы не могли бы сделать простым переупорядочива- нием предложений исходной программы. Например, чтение про- граммы упрощается, если определения областей данных распо- лагаются в исходной программе вблизи использующих их команд. Это особенно справедливо, если программа редактиру- ется или анализируется с помощью терминала. В больших под- программах эту задачу (без помощи программных блоков) можно решить, размещая области данных в удобных местах. ^fOFY дооооооЛоою71 '?Л000000Л1ЕЛ1720бЗЛ4В20МЛ03206(^тООЛШ006Л4ВШВЛЗГ2т^32055ЛОГ2056л010003 ^!Гл00001Е^9л0Г2048л4В2029лЗВ203Е >Л000027Л1^В410ЛВ400/^440Л751010(}0ЛЕ32038Л332ЕЕ^ОВ203^А004/Ч332008Л57А02ЕЛВ850 Тл000044л0^3В2РЕАл13201^4Е0000 {^ООООбСдОДП ^OOOO4^1^41Oa77201^E3201Ba332FF^53AO16aDF2O12aB850a3B2FEF^F0000 < ^00006^04д454Р46а05 жрооооо л Рис. 2.13. Объектная программа, соответствующая рис. 2.11, Однако в этом случае программист должен позаботиться о командах безусловного перехода для обхода таких областей данных. Рассматривая данную ситуацию с машинной точки зрения, желательно располагать фрагменты объектной программы в оперативной памяти в определенном порядке. С другой сторо- ны, человеческая логика подсказывает нам, что предложения исходной программы должны располагаться в другом порядке. Использование программных блоков является одним из спосо- бов удовлетворения обоих этих требований с помощью ассемб- лера, обеспечивающего необходимую реорганизацию. Для того чтобы разместить фрагменты каждого программно- го блока рядом, совершенно необязательно выполнять физиче- скую реорганизацию сгенерированного объектного кода. Ас- семблер может просто записывать объектный код по мере его генерации во время второго просмотра и вставлять соответст- вующий адрес загрузки в каждую запись тела программы. Эти адреса загрузки будут, конечно, отражать наряду с относитель- ным расположением команд и данных внутри блока также и начальный адрес самого блока. Иллюстрация этому дана на рис. 2.13. Две первые записи тела программы генерируются из строк исходной программы с номерами с 5 до 70. Когда в строке 92 встречается предложение USE, ассемблер записывает
2.3. Машинно-независимые характеристики ассемблера 81 текущую запись тела программы (хотя в ней еще есть свобод- ное место). Затем ассемблер подготавливается к открытию но- вой записи тела программы для нового программного блока. Однако ввиду того, что предложения в строках с 95 по 105 не приводят к генерации объектного кода, не создается и соот- ветствующих им записей тела программы. Следующие две Программа, загружаемая Исходная программа Объектная программа а память Рис. 2.14. Программные блоки для рис. 2.11 после выполнения ассемблиро- вания и загрузки. записи тела программы получаются из строк 125—180. На этот раз предложения, принадлежащие очередному программному блоку, действительно требуют генерации объектного кода. Пя- тая запись тела программы содержит один-единственный байт данных, генерируемый из строки 185. Шестая запись тела про- граммы возобновляет непоименованный блок, и оставшаяся часть объектной программы обрабатывается таким же образом. То, что записи тела программы расположены в объектной про- грамме не в порядке возрастания адресов, не имеет значения.
82 Гл. 2. Ассемблеры Загрузчик просто помещает каждую запись по указанному адресу. По завершению загрузки сгенерированный код непо- именованного блока будет занимать относительные адреса с 0000 по 0065; сгенерированный код и зарезервированные обла- сти данных блока CDATA будут занимать адреса с 0066 по 0070, а память, зарезервированная для CBLKS, будет занимать адреса с 0071 по 1070. На рис. 2.14 прослежен процесс ассемб- лирования и загрузки блоков нашей модельной программы. Обратите внимание на то, что предложения программы, обозна- ченные CDATA(l) и CBLKS(l), фактически не присутствуют в объектной программе. Резервирование памяти для этих об- ластей обеспечивается автоматически при загрузке программы с помощью механизма назначения адресов. Для того чтобы убедиться, что вы поняли, как ассемблер обрабатывает программы, состоящие из нескольких блоков, вам следует тщательно изучить сгенерированный код (рис. 2.12) и выполнить ассемблирование еще нескольких команд. А чтобы уяснить, как различные части каждого программного блока собираются вместе, вам следует также воспроизвести (вручную) загрузку объектной программы, приведенной на рис. 2.13. 2.3.5. Управляющие секции и связывание программ В этом разделе мы обсудим, как обрабатываются програм- мы, состоящие из нескольких управляющих секций. Управляю- щая секция — это часть программы, которая после ассемблиро- вания сохраняет свою индивидуальность и может загружаться и перемещаться независимо от других. В отдельные управляю- щие секции чаще всего выделяются подпрограммы или другие логические подразделы программы. Программист может отдель- но ассемблировать и загружать каждую из таких управляющих секций. Достигаемая за счет этого гибкость является основным преимуществом их использования. Примеры этого будут рас- смотрены в гл. 3, когда мы будем обсуждать редакторы связей. Поскольку управляющие секции образуют логически связан- ные части программы, то, следовательно, необходимо предоста- вить некоторые средства для их связывания (linking) друг с другом. Например, команды одной управляющей секции долж- ны иметь возможность ссылаться на команды или области дан- ных, расположенные в другой секции. Такие ссылки нельзя обрабатывать обычным образом, поскольку управляющие сек- ции загружаются и перемещаются независимо друг от друга и ассемблеру ничего не известно о том, где будут расположены другие управляющие секции во время исполнения програм- мы, Такие ссылки между управляющими секциями называются
2.3. Машинно-независимые характеристики ассемблера 83 внешними ссылками {external references). Для каждой внеш- ней ссылки ассемблер генерирует информацию, которая дает возможность загрузчику выполнить требуемое связывание про- грамм. В этом разделе мы опишем, как наш ассемблер обра- батывает внешние ссылки. Реальное выполнение связывания будет детально обсуждаться в гл. 3. На рис. 2.15 показано, как наша модельная программа мо- жет быть записана в виде нескольких управляющих секций. В данном случае имеются три управляющие секции: одна для главной программы и по одной для каждой из подпрограмм. Предложение START определяет начало ассемблируемой про- граммы и задает имя (СОРУ) первой управляющей секции. Первая секция расположена вплоть до предложения CSECT в строке 109. Эта директива ассемблера сигнализирует о начале новой управляющей секции с именем RDREC. Аналогично пред- ложение CSECT в строке 193 начинает управляющую секцию с именем WRREC. Для каждой управляющей секции ассемблер заводит отдельный счетчик размещений (начинающийся с 0) точно так же, как он это делает для программных блоков. От программных блоков управляющие секции отличаются тем, что они обрабатываются ассемблером независимо друг от друга. (Необязательно даже, чтобы все управляющие секции программы ассемблировались одновременно.) Имена, определен- ные в одной управляющей секции, не могут непосредственно ис- пользоваться в другой секции. Для того чтобы загрузчик смог их обработать, они должны быть описаны как внешние ссылки. На рис. 2.15 показаны две директивы ассемблера для задания таких ссылок: EXTDEF (определение внешних имен) и ЕХТ- REF (объявление внешних ссылок). Предложение EXTDEF определяет имена, описанные в данной управляющей секции, но доступные для использования в других секциях. Такие име- на называются внешними именами (external symbols). Имена управляющих секций (в данном случае COPY, RDREC и WRREC) определять в предложении EXTDEF не требуется, так как они автоматически считаются внешними. Предложение EXTREF объявляет имена, которые используются в данной уп- равляющей секции, но определены где-либо еще. Например, имена BUFFER, BUFEND и LENGTH, определенные в управ- ляющей секции с именем СОРУ, делаются доступными другим секциям с помощью предложения EXTDEF в строке 6, В третьей управляющей секции (WRREC), согласно предло- жению EXTREF (строка 207), используются два внешних имени. Порядок перечисления имен в предложениях EXTDEF и EXTREF несуществен. Теперь мы готовы к тому, чтобы посмотреть, как ассемблер обрабатывает внешние ссылки. На рис. 2.16 для каждого пред- ложения программы показан сгенерированный объектный код,
Строка Исходное предложение 5 COPY START 0 КОПИРОВАНИЕ ФАЙЛА 6 EXTDEF BUFFER,BUFEND,LENGTH 7 EXTREF RDREC,WRREC 10 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА 15 CLOOP +JSUB RDREC ВВОД ВХОДНОЙ ЗАПИСИ 20 LDA- LENGTH ПРОВЕРКА НА EOF (LENGTH = 0> 25 COMP ♦0 30 JEQ ENDFIL ВЫХОД, ЕСЛИ НАИЛИ EOF 35 4 JSUB WRREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ 40 J CLOOP ПИКЛ 45 ENDFIL LDA fcC'EOF' ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА 50 STA BUFFER 55 LDA ♦3 УСТАНОВИТЬ LENGTH = 3 60 STA LENGTH 65 4 JSUB WRREC ЗАПИСЬ EOF 70 J BRETADR ВОЗВРАТ ИЗ ПРОГРАММЫ 95 RETADR RESW 1 100 LENGTH RESW 1 ДЛИНА ЗАПИСИ 103 LTORG 105 BUFFER RESB 4096 ДЛИНА БУФЕРА - 4Ф96 БАЙТ 106 BUFEND EQU * 107 MAXLEN EQU BUFEND-BUFFER МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ 109 RDREC CSECT 110 a 115 • ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 122 EXTREF BUFFER,LENGTH,BUFEND 125 CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 130 CLEAR A ОБНУЛЕНИЕ РЕГИСТРА А 132 CLEAR S ОБНУЛЕНИЕ РЕГИСТРА S 133 LDT MAXLEN 135 RLOOP TD INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА 140 JEQ RLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 150 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х'00 155 JEQ EXIT ВЫХОД ИЗ ЦИКЛА ПО КОНЦУ ЗАПИСИ 160 4STCH BUFFER, X ЗАПИСЬ СИМВОЛА В БУФЕР 165 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ 170 JLT RLOOP МАКСИМАЛЬНОЙ ДЛИНЫ 175 EXIT 4STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ 180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 185 INPUT BYTE X'Fl' КОД УСТРОЙСТВА ВВОДА 190 MAXLEN WORD BUFEND-BUFFER 193 WRREC CSECT 195 200 ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 « 207 EXTREF LENGTH,BUFFER 210 CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА 212 4LDT LENGTH 215 WLOOP TD «=XZ05Z Проверка устройства вывода 220 JEQ WLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ 225 4LDCH BUFFER,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 230 WD =XZ05' ВЫВОД СИМВОЛА 235 TIXR T цикл, пока не будут 240 JLT WLOOP ВЫВЕДЕНЫ ВСЕ СИМВОЛЫ 245 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ 255 END FIRST Рис, 2,15, Иллюстрация управляющих секций и связывания программы.
Строка Адрес Исходное ирелжЫанме Шектиый код 3 0000 COPY START 0 4 EXTDEF BUFFER,BUFEND,LENGTH 7 EXTREF RDREC,WRREC 20 0000 FIRST STL RETADR 172027 15 0003 ' CLOOP 4 JSUB RDREC 4B100000 20 0007 LDA LENGTH 032023 25 ллдА WVrt COMP 40 290000 30 000В JEQ ENDFIL 332007 35 0010 4 JSUB WRREC 4B100000 40 0014 J CLOOP 3F2FEC 45 0017 ENDFIL LDA csC'EOF* 032016 50 0014 STA BUFFER 0F2016 155 0013 LDA 43 010003 60 0020 STA LENGTH 0F200A 65 0023 4 JSUB WRREC 4B100000 70 0027 J GRETADR 3E2000 95 0024 RETADR RESW 1 100 0023 LENGTH RESW 1 103 0030 * LTORG -C'EOF' 454F46 105 0033 BUFFER RESB 4096 106 1033 BUFEND EQU A * 107 1000 MAXLEN EQU * BUFEND-BUFFER 109 0000 RDREC CSECT 110 115 ПОДПРОГРАММА ВВОДА ЗАПИСИ НА 120 w 122 EXTREF BUFFER,LENGTH,BUFEND 125 0000 CLEAR X B410 130 0002 CLEAR A B400 132 0004 CLEAR S B440 133 0006 LDT MAXLEN. 77201F 135 0009 RLOOP TD INPUT E3201B 140 000С JEQ ? RLOOP 332FFA 145 000F RD INPUT DB2015 150 0012 COMPR A,S * A004 155 0014 JEQ EXIT 332009 160 0017 4STCH BUFFER,X 57900000 165 001В TIXR T B850 170 001В JLT RLOOP 3B2FE9 175 0020 EXIT 4STX LENGTH 13100000 180 00?4 RSUB 4F0000 185 0027 INPUT BYTE X'Fl' Fl 190 0028 MAXLEN WORD BUFEND-BUFFER 000000 193 0000 WRREC CSECT 195 200 « ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ ВУФЕРА 205 » 207 EXTREF LENGTH,BUFFER 210 0000 CLEAR X B410 212 0002 4LDT LENGTH 77100000 215 0006 WLOOP TD ~X'05f E32012 220 0009 JEQ WLOOP 332FFA 225 АЛАА» vvVb 4LDCH BUFFERrX 53900000 230 0010 WD ~X'05' BF2008 235 0013 TIXR T B850 240 0015 JLT WLOOP 3B2FEE 245 0018 RSUB 4F0000 255 001В END «X'05' FIRST 05 Рис. 2.16, Объектный код для программы на рис. 2,15,
86 Гл. 2. Ассемблеры Для начала рассмотрим команду 15 0003- CLOOP + JSUB RDREC 4В100000 Ее операнд (RDREC) объявлен в предложении EFTREF, и, следовательно, это внешняя ссылка. Ассемблеру ничего неиз- вестно о том, куда будет загружена управляющая секция, со- держащая RDREC, и поэтому он не может обработать адрес- ную часть данной команды. Вместо этого он устанавливает адрес равным 0 и передает загрузчику информацию, позволяю- щую ему занести во время загрузки нужный адрес. Поскольку неизвестно, будет ли адрес RDREC взаимосвязан с чем-либо в данной управляющей секции, относительную адресацию исполь- зовать нельзя. Следовательно, для того чтобы иметь достаточно места для занесения фактического адреса, необходимо исполь- зовать расширенный командный формат. Это справедливо для любой команды, операнд которой ссылается на внешнее имя, Аналогично в команде 160 0017 +STCH BUFFER,X 57 900 000 имеется внешняя ссылка на метку BUFFER. Эта команда ас- семблируется с использованием расширенного командного фор- мата и нулевым адресом. Разряд х установлен в 1, что явля- ется признаком индексной адресации. Предложение 190 0028 MAXLEN WORD BUFEND - BUFFER 000000 лишь незначительно отличается от предыдущих. Здесь значение генерируемой 1-словной константы определяется с помощью выражения, в котором используются две внешние ссылки: BUFEND и BUFFER. Как и ранее, ассемблер заносит для этой константы нулевое значение. Загрузчик во время загруз- ки прибавит к этому значению адрес метки BUFEND, а затем вычтет из него адрес BUFFER, что и даст в итоге требуемое значение. Укажем на различие в обработке выражения в строке 190 и похожего выражения в строке 107. Имена BUFEND и BUFFER определены в той же управляющей секции, что и предложение EQU в строке 107. Поэтому значение этого выра- жения может быть вычислено непосредственно ассемблером. Для строки 190 это сделать нельзя, так как BUFEND и BUF- FER определены в другой управляющей секции и, следователь- но, во время ассемблирования их значения неизвестны. Как видно из предыдущих рассуждений, ассемблер должен запоминать (в SYMTAB), в какой управляющей секции было определено то или иное имя. Любая попытка использовать имя из другой управляющей секции должна отмечаться как ошибка, если только это имя не объявлено (с помощью EXTREF) в качестве внешнего. Ассемблер должен разрешать использовать
2.3. Машинно-независимые характеристики ассемблера 87 одинаковые имена в разных управляющих секциях. Например, противоречивые определения 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 М. Столбцы 2 — 7 Начальный адрес модифицируемого ад- ресного поля относительно начала управ- ляющей секции .(шестнадцатеричный).
88 Гл. 2. Ассемблеры Столбцы 8 — 9 Столбец 10 Столбцы 11 — 16 Длина модифицируемого адресного поля в полубайтах (шестнадцатеричная). Признак модификации (+ или —). Внешнее имя, значение которого надо добавить к заданному полю или вычесть из него. Первые три поля этой записи остались без изменений. Два но- вых поля определяют тип требуемой модификации: сложение или вычитание значения внешнего имени. Имена, используемые для модификации, могут быть определены как в данной управ- ляющей секции, так и в другой. На рис. 2.17 показана объектная программа, соответствую- щая исходному тексту рис. 2.16. Обратите внимание, что для каждой управляющей секции имеется отдельный набор записей объектной программы (от записи-заголовка до записи-конца). Записи каждой управляющей секции в точности такие же, как если бы эти секции ассемблировались раздельно. Записи-определения и записи-ссылки каждой управляющей секции содержат имена, объявленные в предложениях EXTDEF и EXTREF. Запись-определение содержит, кроме того, относи- тельный адрес каждого внешнего имени внутри данной управ- ляющей секции. Для имен, объявленных в EXTREF, информа- ции об адресах нет. Эти имена просто перечисляются в запи- си-ссылке. Давайте теперь изучим процесс связывания внешних ссылок, начиная с ранее рассмотренных исходных предложений. Поле адреса команды JSUB в строке 15 начинается с относительно- го адреса 0004. В объектной программе его начальное значе- ние равно 0. Запись-модификатор М00000405 + RDREC управляющей секции COPY определяет, что адрес метки RDREC должен быть прибавлен к этому полю, что и даст тре- буемый машинный адрес. Две другие записи-модификаторы в секции COPY выполняют сходные функции для команд в стро- ках 35 и 65. Аналогично первая запись-модификатор в управ- ляющей секции RDREC указывает на занесение внешней ссылки для строки 160. Обработка 1-словной константы, сгенерированной по строке 190, мало чем отличается от ранее описанного процесса. Зна- чением этой константы является выражение BUFEND — BUF- FER, где обе метки определены в другой управляющей секции. Ассемблер генерирует эту константу (расположенную по отно- сительному адресу 0028 в управляющей секции RDREC) с ну- левым начальным значением. Две последние записи-модифика- торы в RDREC требуют, чтобы адрес BUFEND был добавлен
2.3. Машинно-независимые характеристики ассемблера 89 к указанному полю, а адрес BUFFER был вычтен. Эти вычис- ления выполняются во время загрузки, и в результате получа- ется требуемая константа. H^OPY ^00000^01033 DaBUFFEI^OOOO3^BUFENDaOO.1O33aLENGTHaOOOO2D ЭДЮВЕС aWRREC ТА000000А10А17202 7А4В100000А032023А2 90000Л332007Л4В10000ЭДШРЕСЛ03201 бдОР2016 ТО 0 0 01 ЦО Da0 10 О О 3А0 F2 0 0Аа4 В10 0 0 0 0 3 Е 2 О О О А АЛ Л Л Л ТА00003ЭД)3А454Р46 1^000004a05a+RDREC maooooha05a+wrrec МОООО2405+WRREС Л АЛ ЕОООООО А h^drec ^0000(^000020 R^UFFER^ENGTHBUFEND ТОООООО1DB41ОВ4ООВ44О7 7201FE3201B3 32FFAJ)B2015A004 33200957 900000аВ850 Л ЛЛЛ.ЛЛ Л Л Д АЛ Л Л T00001D0E3B2FE913 1O0OOOa4F0000aI LOOOQOO л ЛА Л л ЛА Мл0 О О 01 ЭД) 5Д+ В U F FE £ МЛОООО21ДО ^-LENGTH* м^ооогэдоб^-вигЕНо МО 000280 6~ ВиFFЕR Л АЛ Е HWRREC 100000000001С ЛАЛ J^ENGT^BUFFER’ ТООООООа1СаВ41Ол7 71ОООООле32012а332РРАл5390Р000лЙР2008аВ85ЭДЗЁ2рЕЕа4Г0000аЪ5 Ma000003a05a+LENGTH ‘ MaOO0OODaO5+BUFFER АЛЛ Е Рис. 2.17. Объектная программа, соответствующая рис. 2.15. В гл. 3 мы детально рассмотрим, как загрузчик выполняет заданные модификации; сейчас же важно, чтобы вы доскональ- но разобрались с концепциями процесса связывания. Вам сле- дует тщательно изучить другие записи-модификаторы на рис. 2.17 и самостоятельно реконструировать их генерацию по предложениям исходной программы. Обратите внимание, что измененная запись-модификатор мо- жет по-прежнему использоваться для перемещения программ. В случае перемещения модификация заключается в добавлении начального адреса управляющей секции к определенным полям
90 Гл. 2. Ассемблеры в объектной программе. Значением имени, используемого в ка- честве названия управляющей секции, является требуемый ад- рес. Поскольку имя управляющей секции автоматически явля- ется внешним именем, то оно может быть использовано в записи-модификаторе. Так, например, записи-модификаторы рис. 2.8 М.00000705 М00001405 М00002705 будут заменены на записи-модификаторы вида М00000705 + COPY М00001405 + COPY М00002705 + COPY Таким образом, один и тот же механизм может быть использо- ван как для перемещения программ, так и для их связывания. В следующей главе будет рассмотрено еще несколько примеров. Наличие нескольких управляющих секций, каждая из кото- рых может перемещаться независимо от других, создает допол- нительные трудности при обработке выражений. Ранее мы тре- бовали, чтобы все относительные термы выражения группиро- вались в пары (для абсолютного выражения) или чтобы все относительные термы, кроме одного, группировались в пары [(для относительного выражения). Теперь мы вынуждены уси- лить эти ограничения и потребовать, чтобы парные термы при- надлежали одной управляющей секции. Причина этого про- ста — если оба терма определяют относительные метки в од- ной и той же управляющей секции, то их разность является абсолютной величиной (независимо от того, где будет располо- жена управляющая секция). С другой стороны, если метки относятся к различным управляющим секциям, то значение их разности заранее непредсказуемо (и потому, вероятно, беспо- лезно). Например, выражение BUFEND — BUFFER определя- ет длину буфера в байтах. С другой стороны, выражение RDREC — COPY определяет разность между адресами загруз- ки двух управляющих секций. Эта величина зависит от способа размещения программы в оперативной памяти, и маловероятно, чтобы она была полезна в прикладных программах. Когда в выражении используются внешние ссылки, ассемб- лер в общем случае не может определить, корректно оно или нет. Группировка относительных термов для проверки коррект- ности не может быть сделана, если не известно, какие термы принадлежат одной секции, а во время ассемблирования эта информация отсутствует. В этом случае ассемблер вычисляет все известные ему термы и комбинирует их так, чтобы полу-
2.4. Варианты построения ассемблеров 91 чить начальное значение выражения. Кроме того, он генерирует записи-модификаторы, позволяющие загрузчику закончить вы- числение. Загрузчик может проконтролировать корректность выражения. Все эти вопросы мы обсудим в гл. 3 при изучении связывающего загрузчика. 2.4. Варианты построения ассемблеров В этом разделе мы рассмотрим несколько важных вариан- тов общей схемы ассемблирования. Во многих случаях про- граммист не ощущает прямого воздействия схемы ассемблиро- вания на возможности, предоставляемые языком ассемблера, хотя иногда действительно имеются побочные эффекты, кото- рое следует принимать во внимание. В разд. 2.4.1 мы опишем оверлейную структуру, которая обычно используется для двухпросмотровых ассемблеров, В разд. 2.4.2 и 2.4.3 обсуждаются две альтернативные (по от- ношению к стандартной двухпросмотровой) схемы ассемблиро- вания. В разд. 2.4.2 описываются структура и алгоритм одно- просмотрового ассемблера. Такие ассемблеры используются там, где желательно или даже необходимо обойтись без второго просмотра исходной программы. В разд. 2.4.3 дается представ- ление о многопросмотровых ассемблерах. В этих ассемблерах получает свое дальнейшее развитие двухпросмотровая схема ассемблирования, что позволяет обрабатывать ссылки вперед в предложениях определения имен. 2.4.1. Двухпросмотровый ассемблер с I оверлейной структурой Как мы видели, в большинстве ассемблеров процесс обра- ботки исходной программы делится на два просмотра. Внут- ренние таблицы и подпрограммы, которые используются только во время первого просмотра, становятся ненужными после его завершения. Многие подпрограммы и таблицы используются только для одного просмотра и никогда не требуются для дру- гого. В то же время некоторые таблицы (например, SYMTAB)' и подпрограммы (например, поиск в SYMTAB) используются в обоих просмотрах. На рис. 2.18 представлена общая структура двухпросмотро- вого ассемблера. Этот ассемблер состоит из трех сегментов, образующих дерево. Корневой сегмент содержит простую уп- равляющую программу, функцией которой является поочеред- ный вызов двух других сегментов (Просмотр 1 и Просмотр 2)< Корневой сегмент содержит таблицы и подпрограммы, необхо- димые для обоих просмотров,
92 Гл. 2. Ассемблеры Поскольку сегменты Просмотр 1 и Просмотр 2 никогда не требуются одновременно, то во время работы ассемблера они могут занимать одно и то же пространство оперативной памяти. Вначале в память загружается корневой сегмент и сегмент Сегмент Просмотр 1 Сегмент Просмотр 2 Рис. 2.18. Структура двухпросмотрового ассемблера. Просмотр 1, и ассемблер выполняет первый просмотр. После его завершения на место сегмента Просмотр 1 загружается Максимальная память, тре- буемая при оверлейной загрузке Объем памяти, занимаемый всеми под- программами и таблицами ассемблера Управляющая программа Управляющая программа Общие таблицы и подпрог- раммы Резидентная часть Общие таблицы и подпрог- раммы Подпрог- раммы первого просмотра Таблицы первого просмотра Загружаются на одно и то же место в оперативной памяти Подпрог- раммы второго просмотра Таблицы второго просмотра Рис. 2.19. Двухпросмотровый ассемблер с оверлейной структурой. сегмент Просмотр 2. После этого ассемблер выполняет второй просмотр ассемблируемой программы (или промежуточного файла) и завершает свою работу. Этот процесс показан на рис. 2.19. Обратите внимание, что при использовании оверлей- ной структуры ассемблеру требуется значительно меньше
2.4. Варианты построения ассемблеров 93 оперативной памяти, чем если бы одновременно загружались сегменты обоих просмотров. Во многих двухпросмотровых ассемблерах этот прием используется для сокращения требуе- мого объема оперативной памяти. Программа, построенная подобным образом, называется оверлейной программой или программой с перекрытием, так как во время ее исполнения некоторые из ее сегментов пере- крывают другие. В гл. 3 мы более подробно обсудим оверлей- ные программы и рассмотрим, как они обрабатываются загруз- чиком и сервисными процедурами операционной системы. 2.4.2. Однопросмотровые ассемблеры В этом разделе мы рассмотрим структуру и проектирование однопросмотровых ассемблеров. Как мы отмечали в разд. 2.1, основная трудность, возникающая при попытке ассемблировать программу за один просмотр, связана со ссылками вперед. Ча- сто в качестве операндов команд используются имена, которые еще не были определены в исходной программе. Поэтому ас- семблер не знает, какие адреса занести в транслируемую про- грамму. Довольно легко исключить ссылки вперед на данные; до- статочно потребовать,* чтобы все области данных определялись в исходной программе раньше, чем появляются команды, кото- рые на них ссылаются. Это не слишком жесткое ограничение. Программист просто размещает все области данных в начале программы, а не в конце. К несчастью, невозможно столь же легко исключить ссылки вперед на метки команд. Логика про- граммы часто требует передачи управления вперед. (Напри- мер, выход из цикла после проверки некоторого условия.) Тре- бование исключить все такие передачи управления оказалось бы гораздо более жестким и неудобным. Поэтому ассемблер должен предпринимать специальные меры для обработки ссылок вперед. Однако для того, чтобы облегчить задачу, мно- гие однопросмотровые ассемблеры действительно запрещают (или по крайней мере не рекомендуют) ссылки вперед на данные. Существует два основных типа однопросмотровых ассембле- ров. Ассемблеры первого типа записывают объектный код не- посредственно в оперативную память для немедленного испол- нения; ассемблеры второго типа создают объектную програм- му, которая будет выполняться позднее. Обсуждение обоих типов мы проиллюстрируем с помощью программы на рис. 2.20. Это тот же самый пример, что и на рис. 2.2, но все определения данных расположены перед командами, в которых они исполь- зуются. Сгенерированный объектный код показан на рис. 2.20 только для справки, Мы обсудим, как однопросмотровый
94 Гл. 2. Ассемблеры Строка Адрес Исходное предложение Объектный КОД 0 1000 COPY START 1000 1 1000 EOF BYTE C'EOF' 454F46 •? 1003 THREE WORD 3 000003 3 1006 ZERO WORD 0 000000 4 1009 RETADR RESW 1 5 100С LENGTH RESW 1 6 100F BUFFER RESB 4096 9 - 30 200F FIRST STL RETADR 14100? 15 2012 CLOOP JSUB RDREC 48203R 20 2015 LDA LENGTH 001000 25 2018 COMP ZERO 281006 30 201В JEQ ENDFIL 302024 35 201Е JSUB WRREC 482062 40 2021 J CLOOP 3C2012 45 2024 ENDFIL LDA EOF 001000 50 2027 STA BUFFER 0C100F 55 202А LDA THREE 001003 60 202D STA LENGTH 0C100C 65 2030 JSUB WRREC 482062 70 2033 LDL RETADR 08100? 75 2036 RSUB 4C000fc 110 • 115 1» ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 121 2039 INPUT BYTE X'Fl' Fl 122 203А MAXLEN WORD 4096 001000 124 125 203D RDREC LDX ZERO 041006 130 2040 LDA ZERO 001006 135 2043 RLOOP TD INPUT E0203? 140 2046 JEQ RLOOP 302043 145 2049 RD INPUT D8203? 150 204С COMP ZERO 281006 155 204F JEQ EXIT 30205B 160 2052 STCH BUFFERrX 54900F 165 2055 TIX MAXLEN 2C203A 170 2058 JLT RLOOP 382043 175 205В EXIT STX LENGTH 10100C 180 205Е RSUB 4C0000 195 * 200 ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ ВУФЕРА 205 206 2061 OUTPUT BYTE X'05' 05 205 210 2062 WRREC LDX ZERO 041006 215 2065 WLOOP TD OUTPUT ЕФ2061 220 2068 JEQ WLOOP 302065 225 206В LDCH BUFFER, X 50900F 230 206Е WD OUTPUT DC2061 235 2071 TIX LENGTH 2C100C 240 2074 JLT WLOOP 382065 245 2077 RSUB 4C0000 255 END FIRST Рис. 2.20. Модельная программа для однопросмотрового ассемблера.
2.4. Варианты построения ассемблеров 95 ассемблер каждого типа мог бы в действительности сгенериро- вать требуемую объектную программу. Вначале мы рассмотрим однопросмотровые ассемблеры, генерирующие объектный код непосредственно в оперативную память для немедленного исполнения. В этом случае не созда- ется объектная программа и не требуется загрузчик. Такие ассемблеры типа загрузка — выполнение полезны в системах, ориентированных на разработку и тестирование программ. Ти- пичным примером такой системы может служить университет- ская студенческая система. В такой системе значительную долю от общего объема работы составляет трансляция программ. Поскольку практически при каждом новом запуске программ происходит их переассемблирование, эффективность процесса ассемблирования приобретает важное значение. Ассемблеры типа загрузка — выполнение позволяют избежать накладных расходов, связанных с записью объектной программы на внеш- нюю память и ее последующим считыванием. Такой процесс может быть организован как однопросмотровым, так и двухпросмотровым ассемблером. Однако однопросмотровый ассемблер позволяет избежать также и накладных расходов, связанных с дополнительным просмотром исходной про- граммы. Поскольку генерируемая объектная программа вместо того, чтобы записываться на внешнюю память, располагается в опе- ративной памяти, то обработка ссылок вперед оказывается ме- нее сложной. Ассемблер просто генерирует команды объектного кода по мере просмотра исходной программы. Если операндом команды является еще неопределенное имя, то при ассемблиро- вании команды обработка ее адресной части пропускается. Имя, использованное в качестве операнда, заносится в таблицу имен (если оно не было занесено ранее). Строка таблицы по- мечается признаком, указывающим на то, что данное имя еще не определено. Адрес операндного поля команды, ссылающейся на неопределенное имя, добавляется в список ссылок вперед, связанный с соответствующим элементом таблицы имен. Если встречается определение имени, то просматривается его список ссылок вперед (если он есть) и требуемый адрес заносится в каждую предварительно сгенерированную команду. Рассмотрим пример, который поможет прояснить этот про- цесс. На рис. 2.21а показано, как будут выглядеть объектный код и элементы таблицы имен после просмотра строки 40 программы на рис. 2.20. Первая ссылка вперед была в стро- ке 15. Поскольку операнд (RDREC) еще не был определен, то команда ассемблировалась без назначения адреса для этого операнда (на рисунке обозначено как-----------). Затем имя RDREC было занесено в SYMTAB как неопределенное имя [{обозначено а адрес операндного поля команды (2013)?
96 Гл. 2. Ассемблеры Адрес. памяти Содержимое 1ООО 454F4600 00030000 ООхххххХ ХххххххХ 1010 ХХХХХХХХ ХХХХХХХК ХХХХХХХХ ХХХХХХХХ Имя Значение LENGTH 100C RDREC ♦I*- —H201310 THREE 1003 ZERO 1006 WRREC * I •“ —* 201 Fl ° I EOF 1000 ENDFIL *l- —> 201C 0 RETADR 1009 BUFFER 10QF CLOOP 2012 FIRST 200F 2000 хххххххх ХХХХХХХХ ХХХХХХХХ ХХХХХХ14 2010 2020 ж 100948— —302012 —0Q10QG 28100630 —48— Адрес памяти Содержимое Имя Значение 1000 454F460Q 00030000 ООхХХХХХ ХХХХХХХХ Ю10 хххххххх хххххххх хххххххх хххххххх 2000 ХХХХХХХХ ХХХХХХХХ ХХХХХХХХ ХХХХХХ14 2010 10094820 3D00100C 28100630 202448— 2020 —302012 0010000С 100F0010 030C100C 2030 48 -08 10094COG OOFIOOIO 00041006 2040. ООЮ06ЕО 20393Q2Q 43D82039 281QQ63Q 2050 ——5490 ОР Г 4 LENGTH 100C RDREC 203D THREE 1003 ZERO 1006 WRREC * | •- 201F 2031 0 EOF 1000 ENDFIL 2024 RETADR 1009 BUFFER 100F CLOOP 2012 FIRST 200F MAXLEN 203A INPUT 2039 EXIT ♦I- 2050 | RLOOP* 2043 6 Рис. 2.21. Объектные коды, записанные в оперативную память, и эле- менты таблицы имен после просмотра строки 40(a) и строки 160 (б) програм- мы на рис. 2.20,
2.4. Варианты построения ассемблеров 97 был занесен в список, связанный с RDREC. Аналогично обра- батывались команды в строках 30 и 35. Рассмотрим теперь рис. 2.216, соответствующий ситуации после просмотра строки 160. К этому моменту была разреше- на часть ссылок вперед, в то время как другие были добавле- ны. Когда было определено имя ENDFIL (строка 45), ассемб- лер занес его значение в SYMTAB. Затем он занес это значе- ние в операндное поле команды (по адресу 201С), как это предписано списком ссылок вперед. С этого момента все ссыл- ки на ENDFIL не будут являться ссылками вперед и не будут заноситься в список. Аналогично определение RDREC (стро- ка 125) позволило заполнить операндное поле по адресу 2013. Тем временем были добавлены две новые ссылки вперед: WRREC (строка 65) и EXIT (строка 155). Вам следует про- должить этот процесс до конца программы и самостоятельно убедиться в том, что все ссылки вперед будут занесены пра- вильно. Когда будет обнаружен конец программы, все элемен- ты SYMTAB с признаком * будут указывать на неопределен- ные имена, и, следовательно, ассемблер должен отметить их как ошибки. По достижению конца программы ассемблирование завер- шается. Если не было ошибок, то ассемблер ищет в SYMTAB значение имени, указанное в предложении END (в данном слу- чае FIRST), и передает управление на данный адрес для испол- нения ассемблированной программы. В нашем примере мы использовали абсолютную программу, так как для ассемблера типа загрузка — выполнение фактиче- ский адрес загрузки должен быть известен во время ассемб- лирования. Конечно, необязательно, чтобы адрес загрузки опре- делялся программистом; он может назначаться и системой. Это не влияет на процесс ассемблирования, так как в любом случае в счетчик размещений будет занесен фактический адрес начала программы. Однопросмотровые ассемблеры, результатом работы которых является объектная программа, необходимы в системах, где отсутствуют рабочие внешние запоминающие устройства для хранения промежуточного файла между двумя просмотрами. Такие ассемблеры также могут быть полезны, когда внешняя память медленна или неудобна для использования по каким- либо другим причинам. Работа однопросмотровых ассемблеров, производящих объектные программы, несколько отличается от описанной ранее. Как и прежде, ссылки вперед заносятся в список. Однако теперь, когда будет встречено определение имени, команды, ссылающиеся на это имя, могут уже отсут- ствовать в оперативной памяти и, следовательно, будут недо- ступны для модификации. В общем случае они будут уже за- писаны на внешнее устройство как часть тела объектной про*
98 Гл. 2. Ассемблеры граммы. Поэтому ассемблер должен сгенерировать другую запись тела программы с соответствующим адресом команды, а загрузчик занесет этот адрес в команду во время загрузки программы. Иллюстрация этого процесса дана на рис. 2.22. Вторая запись тела программы содержит код, сгенерированный для строк 10—40 исходной программы, изображенной на рис. 2.20< Адреса операндов команд для строк 15, 30 и 35 сгенерированы с нулевым значением. Когда в строке 45 встречается определен ние имени ENDFIL, ассемблер генерирует третью запись тела НЛС0РУ ^01000^01074 ^001000Л09Л454Г46Л000003Л000000 ^00200^15Л141009Л480000Л00100СЛ281006Л300000Л48000(^ЗС2012 Т00201С022024 -Л л л TpQ2024l9O0100OOClOOFO01Q03OCI00C48000OO810094c000OF1001000 А /V л л л л д л л л 5^00201^0^2030 5^0203»Л1ЕЛ041006Л00106^Е0203^30204^1)8203^281006Л300000Л54900^2С203АЛ382043 1Л00205<^02Л2053 Т00205В0710100С4С000005 л л л л л 3^02011^2062 ^002031л02л2062 3.00206218041006E02061.3020655Q900EBC2Q6T2CIOOC3820654COOOO л л л Л АЛЛ Л Д Л ЕдО0200В Рис. 2.22. Объектная программа, полученная из исходной программы на рис, 2.20 с помощью однопросмотрового ассемблера. программы. Эта запись определяет, что значение 2024 (адрес ENDFIL) должно быть занесено по адресу 201С (адресное поле операнда команды JEQ в строке 30). Таким образом, во время загрузки программы величина 2024 заменит ранее за- несенное значение 0000. Точно так же обрабатываются и дру- гие ссылки вперед нашей программы. В результате для раз- решения ссылок вперед, которые не могут быть обработаны ассемблером, используются средства загрузчика. Конечно, при передаче объектной программы загрузчику должен быть сохра- нен первоначальный порядок ее записей. В этом разделе мы рассмотрели только простые однопро- смотровые ассемблеры для обработки абсолютных программ Мы предполагали, что в качестве операндов команд использу- ются одиночные имена, а ассемблированные команды содержат фактические (не относительные) адреса операндов. Более раз- витые средства ассемблера, такие как литералы, были заире- Ч Автор имеет в виду программы в абсолютных адресах. — Прим, ред.
2.4. Варианты построения ассемблеров 99 щены. Вам предлагается подумать о способах снятия некото- рых из этих ограничений (некоторые предложения можно най- ти в упражнениях к данному разделу) , 2,4.3. Многопросмотровые ассемблеры При обсуждении директивы ассемблера EQU мы требовали, чтобы все имена в правой части (т. е. в выражении, задающем значение нового имени) были предварительно определены в ис- ходной программе. Аналогичные ограничения накладывались и на директиву ассемблера ORG. В действительности такие огра- ничения обычно накладываются на все директивы ассемблера, которые (прямо или косвенно) определяют имена. Причина этого кроется в самом процессе определения имен, который используется в двухпросмотровом ассемблере, Рассмо- трим следующую последовательность; ALPHA EQU BETA BETA EQU DELTA DELTA EQU 1 Когда во время первого просмотра встретится метка ВЕТА, мы не сможем назначить ей адрес, так как DELTA еще , не опре- делена. Поэтому мы. не. сможем вычислить, значение метки ALPHA во время второго просмотра. Это означает, что любой ассемблер, выполняющий только два последовательных просмо- тра исходной программы, не может обрабатывать такие после- довательности определений. Ограничения, связанные с запрещением ссылок вперед в определениях имен, обычно не вызывают у программиста’серьез- ных неудобств. В действительности подобные ссылки вперед скорее порождают сложности у человека, читающего програм- му, чем у ассемблера. Тем не менее некоторые ассемблеры проектируются так, чтобы исключить необходимость таких огра- ничений. Общее решение заключается в использовании много- просмотрового ассемблера, который может сделать столько проч смотров, сколько необходимо для выполнения процесса опреде- ления имен. Совершенно необязательно, чтобы такой ассемб- лер просматривал всю программу целиком более двух раз. Вместо этого фрагменты программы, содержащие ссылки впе- ред, запоминаются во время первого просмотра. Дополнитель- ный просмотр таких запомненных определений делается по мере продвижения процесса ассемблирования. По завершению этого процесса выполняется обычный второй просмотр. Существует несколько способов решения обрисованной выше задачи. Метод, который мы опишем, заключается в запомина- нии в таблице имен тех определений, которые содержат ссыл- ки вперед. Для того чтобы облегчить вычисление значений 4*
too Гл. 2. Ассемблеры X HALFSZ EQU MAXLEN/2 2 ШХЕЕИ EQU BUFEND-BUFFER 3 PREVBT EQU BUFFER-1 4 BUFFER RESB 4096 5 BUFEND EQU ♦ 6 BUFEND | * | •- И MAXLEN I ° 1 HALFSZ |&l] MAXLEN/2 |o MAXLEN |&21 BUFEND-BUFFER | * HHALFSZ M BUFFER | * | ►[MAXLEN [°1 О
2.4. Варианты построения ассемблеров 101 д Рис, 2,23. Пример работы многопросмотрового ассемблера.
102 Гл. 2. Ассемблеры _ --------------- ------------------------, имен, элементы данной таблицы содержат признак, который указывает на то, что данное имя зависит от значений других имен. На рис. 2.23а показана последовательность предложений, определяющих имена, в которых используются ссылки вперед; остальная часть исходной программы для нашего обсуждения интереса не представляет и потому опущена. Последующие ча- сти рис. 2.23 показывают, как могла бы выглядеть информа- ция, занесенная в таблицу имен, после обработки каждого из приведенных исходных предложений. На рис. 2.236 приведены элементы таблицы имен, получив- шиеся после первого просмотра предложения 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) осталось неопределенным, Другое имя
2.5. Примеры реализации 103 списка (PREVBT) можно вычислить, так как оно зависит толь- ко от BUFFER. Выражение, определяющее значение PREVBT, вычисляется и запоминается в SYMTAB, Результат показан на рис. 2.23д. Дальнейшая обработка осуществляется аналогично. Когда в строке 5 определяется BUFEND, его значение заносится в таб- лицу имен. Список, связанный с BUFEND, предписывает ас- семблеру вычислить MAXLEN, а занесение значения MAXLEN вызывает, в свою очередь, вычисление имен из связанного с ним списка (HALFSZ). Как показано на рис. 2.23е, на этом процесс определения имен заканчивается. Если какие-либо име- на остаются неопределенными, то они отмечаются как оши- бочные. Процедура, которую мы только что описали, применяется к именам, определяемым с помощью директив ассемблера типа EQU. Вам следует подумать, как модифицировать этот метод для того, чтобы он позволял обрабатывать ссылки вперед и в предложениях ORG, 2.5. Примеры реализации В предыдущих разделах мы обсудили многие самые общие средства ассемблеров. Однако разнообразие машин и языков ассемблера очень велико, В большинстве ассемблеров имеются нестандартные средства, обусловленные структурой машины или особенностями языка. В этом разделе будут рассмотрены примеры трех ассемблеров реальных машин. Очевидно, что в рамках одного раздела невозможно дать полного описания какого-либо из этих ассемблеров. Вместо этого мы сфокусиру- ем внимание на наиболее интересных или необычных средствах каждого из них и выделим те аспекты конкретного проекта, где базовые алгоритмы и структуры таблиц отличаются от описанных ранее. Мы обсудим примеры ассемблеров для System/370, VAX и CYBER. Прежде чем продолжить чтение данного раздела, вам следует посмотреть в гл. 1 описания этих машин. 2J5.1. Ассемблер System/370 Относительная базовая адресация в System/370 использует- ся с той же целью, что и в УУМ/ДС,— для экономии места, занимаемого машинной командой. Полный адрес System/370 занимает 24 разряда. Вместе с тем тот же самый адрес можно закодировать с помощью номера базового регистра (4 разряда) и смещения (12 разрядов). Однако есть и отличие: в Sy- stem/370 нет ни адресации относительно счетчика команд, ни
104 Гл. 2. Ассемблеоы чего-либо похожего на расширенный командный формат УУМ/ДС. Поэтому все команды, ссылающиеся на оперативную память, должны использовать относительную базовую адреса- цию. Как следствие этого при перемещении объектной про* граммы System/370 модификация требуется только для эле- ментов данных, значениями которых являются фактические адреса (т. е. для адресных констант). В System/370 любой регистр общего назначения (за исклю- чением R0) может использоваться в качестве базового регист- ра. Решение о том, какие регистры использовать для этой цели, возлагается на программиста. В больших программах обычно одновременно используются несколько различных базовых ре- гистров. Какие регистры могут использоваться в качестве базо- вых и каково их содержимое, программист сообщает при помо- щи директивы ассемблера USING. По своим функциям она по- хожа на директиву BASE языка ассемблера УУМ/ДС. Таким образом, предложения USING LENGTH,R1 USING BUFFER,R4 определяют в качестве базовых регистры R1 и R4. Предполага- ется, что R1 будет содержать адрес LENGTH, a R4 — адрес BUFFER. Так же как и в УУМ/ДС, программист должен предусмотреть команды, обеспечивающие загрузку этих регист- ров во время выполнения программы. Дополнительные предло- жения USING можно помещать в любом месте программы. Если базовый регистр в дальнейшем необходимо использовать для других целей, то программист с помощью предложения DROP может сообщить ассемблеру, что дальнейшее использо- вание регистра в качестве базового запрещено. Дополнительная гибкость в использовании регистров влечет за собой увеличение объема работы, выполняемой ассемблером. Для того чтобы знать, какие регистры общего назначения мо- гут в данный момент использоваться в качестве базовых ре- гистров и каково их значение, используется таблица базовых регистров. При выполнении предложения USING в таблицу за- носится новый элемент (или модифицируется уже существую- щий); при выполнении предложения DROP соответствующий элемент удаляется из таблицы. Для каждой команды, операнд которой является адресом оперативной памяти, ассемблер ищет в таблице подходящий для адресации базовый регистр. Если для адресации подходит более одного регистра, то выбирается тот, который дает наименьшую величину смещения. Если ни один из регистров не подходит, то ассемблирование команды невозможно. Процесс вычисления смещения в точности такой же, как и для УУМ/ДС.
2.5. Примеры реализации 105 Наряду с этим в языке ассемблера System/370 разрешено явное задание базового регистра и смещения непосредственно в исходном предложении. Например, в предложении L R2,8(R4) в качестве адреса операнда задан адрес смещенный на 8 байт относительно адреса, содержащегося в R4. Такая форма адре- сации может быть полезна, если известно, что некоторый ре- гистр содержит начальный адрес таблицы или записи данных, а программист хотел бы сослаться на определенную область внутри этой таблицы или записи. При обработке команд, в ко- торых явно заданы базовый регистр и смещение, ассемблер просто заносит заданные значения в объектный код (в данном случае базовый регистр R4 и смещение 8). Таблица базовых регистров в этом случае не требуется; поэтому регистр, исполь- зуемый подобным образом, не нужно описывать в предложении USING. Другой проблемой, которая возникает в ассемблере Sy- stem/370, является выравнивание областей данных и команд. Некоторые команды выполняются более эффективно, если их операнды выравнены по границам памяти, соответствующим их длинам. Например, операнд длиной в одно слово (32 разряда) должен начинаться с байта, адрес которого кратен 4, а операнд длиной в полслова (16 разрядов) —с адреса, кратного 2. Точ- но так же требуется, чтобы машинная команда начиналась с байта, адрес которого кратен 2. Заботу о таком выравнивании берет на себя ассемблер, продвигая, если это необходимо, счетчик размещений. Предпо- ложим, например, что счетчик размещений содержит 0015 и необходимо разместить элемент данных длиной в одно слово. В этом случае, прежде чем резервировать память для этого слова, счетчик размещений следует увеличить до 0018 (ближай- шее кратное 4 больше чем 0015). Байты объектной программы, пропущенные для того, чтобы обеспечить соответствующее вы- равнивание, называются пассивными байтами (slack bytes')« (В некоторых ассемблерах System/370 включение или выклю- чение выравнивания задается по выбору во время ассемблиро- вания.) Пассивные байты являются для объектной программы поте- рянной памятью. Величина этих потерь существенно зависит от того, в каком порядке определяются элементы данных. Рас- смотрим, например, последовательность предложений резерви- рования памяти, изображенную на рис. 2.24а. Если при обра- ботке первого предложения счетчик размещений содержит, как это показано, адрес, кратный 4, то для размещения с вырав- ниванием потребуется 20 байт памяти. Так как сами данные занимают только 14 байт, то это означает, что внесение
106 Гл. 2. Ассемблеры Адрес Исходное предложение «024 A DS С «025 0028 В DS F 002С С DS ЗС 002F 0030 В BS Н 0032 0034 Е DS F 0038 1 ВАЙТ (3 пассивных байта) СЛОВО 3 БАЙТА (1 пассивный байт) ПОЛУСЛОВО <2 пассивных байта) СЛОВО а Адрес Исходное предложение 0024 В DS F СЛОВО 0028 Е DS F СЛОВО 002С D DS H ПОЛУСЛОВО 002Е С BS ЗС 3 БАЙТА 0031 A BS С 1 БАЙТ .0032 Рис. 2.24. Влияние выравнивания данных на требуемый объем оперативной памяти. пассивных байтов увеличило объем занимаемой оперативной памяти почти на 50%. Если сгруппировать элементы данных, требующие одинакового выравнивания, то можно значительно сократить эти накладные расходы. Например, если те же опре- деления расположить в порядке, показанном на рис. 2.246, то не потребуется ни одного пассивного байта. Ассемблер мало что может сделать с последовательностью определений данных, написанной программистом. В то же вре- мя размещение литеральных операндов непосредственно конт- ролируется ассемблером. Большинство ассемблеров System/370 располагают элементы данных в литеральном пуле так, чтобы минимизировать потери памяти. Вначале назначаются адреса
2.5. Примеры реализации 107 для всех литералов, которые требуют для своего размещения двойное слово, затем — слово, полуслово и т. д. Ассемблер 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 SEC- Tion). В DSECT может быть использовано любое предложение языка ассемблера, однако эти предложения не становятся
108 Гл. 2. Ассемблеры частью объектной программы. Чаще всего DSECT используется для описания структуры сложных областей данных (записей, •таблиц и т. п.), определенных вне данной программы. Метки, использованные в DSECT, определяют имена, которые могут быть использованы для адресации полей записи или таблицы (после того как будет задан подходящий базовый регистр). Дополнительную информацию о типовом ассемблере Sy- stem/370 можно найти в IBM [1979], IBM [1974] и IBM Ц1982]. 2.5.2. Ассемблер ЭВМ VAX В ЭВМ VAX используется намного более гибкий метод ко- дирования операндов, чем в большинстве машин. Имеется не- обычно много способов адресации, и (за небольшим исключе- нием) каждый из этих способов может быть использован в лю- бой команде. Спецификаторы операндов, связанные с различ- ными способами адресации, занимают различный объем памя- ти. Как мы увидим, это увеличивает объем работы, выполняе- мой ассемблером. * Терминология, используемая в языке ассемблера VAX, не- сколько отличается от стандартного набора терминов, который мы использовали. Например, есть два способа адресации, в которых значения операнда хранятся как часть команды,— не- посредственный и литеральный. (Для операндов этого типа мы использовали в разд. 2.2 термин непосредственная адресация.) Функционально эти два способа эквивалентны, и в исходной программе они задаются с помощью одной и той же нотации. Во время первого просмотра ассемблер, анализируя величину операнда, решает, какой способ адресации использовать. Если операнд является целым между 0 и 63 (или какой-либо другой величиной, которую можно разместить в 6-разрядном поле), то используется литеральный способ. Если операнд нельзя разме- стить в 6-разрядном поле или ассемблер еще не может опреде- лить его значение, то выбирается непосредственный способ. При непосредственном способе длина спецификатора операнда зави- сит от типа операнда, используемого в команде (слово, длинное слово и т. д.). Длина спецификатора операнда для литерально- го способа всегда равна одному байту. Обратите внимание, что операнды VAX, использующие лите- ральный способ адресации, обрабатываются совершенно иначе, чем литералы, о которых мы говорили в разд. 2.3. В языке ассемблера VAX нет никаких средств, которые бы соответство- вали тому, что мы называли литералами в нашем ассемблере для УУМ/ДС. Имеется еще ряд случаев, когда длина спецификатора опе- ранда (а следовательно, и длина команды) должна определять-
2.5. Примеры реализации tog? ся ассемблером. Например, при использовании способа адреса* ции относительно счетчика команд поле смещения может ко- дироваться как байт, слово или длинное слово. Ассемблер; выбирает длину этого поля в соответствии с величиной требуе- мого смещения. Если в операнде команды какое-либо имя еще не определено, то ассемблер во время первого просмотра не может вычислить смещение. В этом случае выбирается смеще* ние максимально возможного размера (длинное слово). Заме* тим, что это решение нельзя отложить до второго просмотра, так как длина команды влияет на значения всех меток, опре* деленных в программе ниже данной команды. Величина смещения для адресации относительно счетчика команд вычисляется так, как это было описано в разд. 2.2, Однако в момент вычисления целевого адреса во время испол*. нения программы счетчик команд будет содержать не адрес следующей команды, а адрес следующего спецификатора one- ранда (если он есть). Это необходимо учитывать при вычис* лении величины смещения. В большинстве языков ассемблера длина ассемблированной команды однозначно определяется по ее мнемоническому коду; операции. Для ЭВМ VAX это не так. Каждый спецификатор, операнда VAX в зависимости от типа операнда может иметь длину от 1 до 9 байт. Например, в команде MOVBRI,R5 оба операнда находятся в регистрах. Каждый спецификатор операнда требует 1 байт; поэтому ассемблированная команда будет занимать 3 байт. В то же время в команде MOVB 1,8 (R6) для первого операнда используется литеральный способ адре- сации (длина спецификатора 1 байт), а для второго — базовая относительная адресация. С учетом величины смещения (8) для спецификатора второго операнда требуется 2 байт; таким об* разом, вся ассемблированная команда будет занимать 4 байт, В команде MOVB Rl, ALPHA для спецификатора первого операнда требуется 1 байт. Для второго операнда используется адресация относительно счетчи* ка команд. В зависимости от величины смещения его специфик катор может занимать от 2 до 5 байт. Таким образом, в зави* симости от адреса, назначенного метке ALPHA, данная команда может занимать от 4 до 7 байт. Это означает, что ассемблер ЭВМ VAX должен во время первого просмотра выполнять значительно более сложную
110 Гл. 2. Ассемблеры работу, чей, скажем, ассемблер 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 можно найти в DEC [1982] и DEC [1979].
2.5. Примеры реализации ИТ * 2.5.3. Ассемблер ЭВМ CYBER Язык ассемблера ЭВМ CYBER, который называется COM- PASS, имеет несколько существенных отличий от других язы- ков. Первое отличие является прямым следствием структуры машины. Дело в том, что размер машинного слова CYBER по- зволяет разместить в нем несколько различных ассемблирован- ных команд. В общем случае задачей ассемблера является упаковка как можно большего количества последовательно рас- положенных команд в одно слово объектной программы. Ко- нечно, можно было бы просто размещать по одной машинной команде в слове, а неиспользованное пространство заполнять невыполняемыми командами, но такое решение весьма неэф- фективно с точки зрения как занимаемого пространства, так и времени исполнения. Для упаковки команд в ассемблере предусмотрен счетчик позиций (position counter). Этот счетчик показывает количество свободных разрядов, оставшихся в текущем слове объектной программы. В начале нового слова в счетчик позиций заносится число 60 (число разрядов слова CYBER). При генерации команды количество разрядов, требуемое для ее размещения, вычитается из счетчика позиций. Если в текущем слове нет места для размещения очередной команды, то оставшиеся раз- ряды (если они есть) заполняются неисполняемыми командами, а для работы берется новое слово объектной программы. В этот момент счетчик размещений и счетчик слов (его мы опишем в этом разделе ниже) увеличиваются на 1, после чего они будут содержать адрес следующего слова. Предыдущее описание представляет собой обычую после- довательность действий: команды (или элементы данных) упа- ковываются в машинное слово до тех пор, пока это возможно, а затем осуществляется переход к следующему слову. Однако в некоторых случаях требуется перейти к следующему слову несмотря на то, что в текущем слове еще достаточно места для размещения следующего элемента объектного кода. Такая не- обходимость является следствием того, что в CYBER применя- ется пословная, а не побайтная адресация оперативной памяти. Рассмотрим, например, команду безусловного перехода. Ад- рес операнда в данной команде должен быть адресом слова. После того как в счетчик команд будет занесен адрес, задан- ный в команде перехода, следующей командой, выбираемой для выполнения, будет первая команда, упакованная в слово с адресом, равным целевому адресу команды перехода. Это озна- чает, что мы можем непосредственно передать управление толь- ко на команду, расположенную в начале слова объектной про- граммы.
112 Гл. 2. Ассемблеры Процесс размещения команды или элемента данных в на- чале слова, даже если в предыдущем слове достаточно места, называется проталкиванием (forsing upper). При этом остаток предыдущего слова заполняется неисполняемыми командами, счетчик слов и счетчик размещений увеличиваются на 1, а в счетчик позиций заносится число 60. Ассемблер COMPASS вы- полняет проталкивание в следующих случаях: 1. Ассемблируемое предложение имеет метку (так как ад- рес метки указывает на начало слова). 2. Ассемблируемое предложение является предложением ре- зервирования памяти, и в нем не задана упаковка битовых по- лей в одно слово. 3. Предыдущее предложение является предложением безус- ловного перехода (в этом случае единственный способ выпол- нить следующую команду — передать на нее управление). Программист может задать проталкивание для любого предло- жения, задав в поле метки символ +• Для того чтобы отменить автоматическое проталкивание, достаточно задать в поле мет- ки символ —. Кроме счетчика позиций ассемблер использует в своей ра- боте еще два счетчика — счетчик размещений и счетчик слов. Вместе эти два счетчика служат для того же, что и LOCCTR, описанный в разд. 2.1. Основная функция счетчика слов та же, что и у LOCCTR: счетчик слов вместе со счетчиком позиций указывает на относительное местоположение в объектном коде следующей ассемблируемой команды. Счетчик размещений ис- пользуется ассемблером для назначения адресов внутренним меткам программы. Ранее для этого мы также использовали LOCCTR. Обычно значения счетчика слов и счетчика размещений со- впадают. Однако, если это необходимо, программист может с помощью директив ассемблера изменить значение счетчика раз- мещений. Это может оказаться полезным только в весьма спе- цифической ситуации когда при ассемблировании объектный код размещается в одном месте, а перед исполнением должен быть перемещен в другое. В этом случае программист изменяет значение счетчика размещений так, чтобы оно соответствовало адресу расположения объектного кода на момент исполнения. На рис. 2.25 показан пример такого использования счетчика размещений. Данная программа содержит большой буфер, ис- пользуемый во время ее исполнения для ввода и вывода. Про- грамма состоит из трех различных разделов, которые выбира- ются при вызове программы. Однако только один из этих разделов используется при исполнении программы. Исполняе- мые команды для каждого из трех разделов представлены в исходной программе в виде отдельных частей. Объектный код,
2.5. Примеры реализации 113 полученный в результате трансляции каждой из этих частей, занимает значительный объем оперативной памяти. Если бы объектная программа была загружена вместе с бу- фером и тремя разделами, то она потребовала бы большого объема памяти. Для экономии места мы можем воспользовать- ся приемом, показанным на рис. 2.25а. Объектный код для Раздела 1 (наибольший раздел) ассемблируется и загружается обычным образом. В то же время код для Раздела 2 и Раз- дела 3 загружается в область буфера. Эта область не исполь- зуется до тех пор, пока не начнется ввод, поэтому первоначаль- ное размещение команд в этой области проблем не вызывает. Если для выполнения будет выбран Раздел 2 или Раздел 3, то соответствующий раздел объектного кода будет перемещен программой из области буфера на место кода Раздела 1 (рис. 2.256). Теперь требуемый раздел выбирается просто вы- зовом объектного кода, а буфер может использоваться для вво- да данных. Программист должен быть уверен, что при таком перемеще* нии объектного кода метки будут определены правильно. Рас- смотрим, например, команду с меткой SKIP2 на рис. 2.25в /строка 11). Поскольку счетчик слов был переустановлен (строка 7), то код Разделов 2 и 3 будет расположен внутри буфера. Если бы программа ассемблировалась обычным обра- зом, то в этом случае значением метки SKIP2 был бы адрес внутри буферной области. Однако фактически во время испол- нения команды этого раздела будут расположены в памяти, общей для всех трех разделов. Поэтому целевой адрес в коман- де перехода в строке 10 должен быть адресом в этой области, а не в области буфера. Эта проблема решается с помощью задания в качестве но- вого значения счетчика размещений начального адреса буфер- ной области. Напомним, что в данном ассемблере для присваи- вания меткам значений используется счетчик размещений, а не счетчик слов. Поэтому метке SKIP2 будет присвоено значение, соответствующее адресу, по которому она будет расположена во время исполнения, а не адресу на момент ассемблирования^ Аналогично обрабатывается код Раздела 3. После того как все три раздела будут оттранслированы, первоначальное значение счетчика слов восстанавливается с помощью предложения ORG в строке 18. Предложение в строке 19 устанавливает зна- чение счетчика размещений равным значению счетчика слов, и обычный процесс ассемблирования будет продолжен. В COMPASS предусмотрены программные блоки (их назы- вают подпрограммными блоками — subprogram bloks) и управ- ляющие секции (их называют подпрограммами — subprograms)^ Их обработка в основном схожа с процессом, описанным в разд. 2.3, за исключением того, что все литералы размещаются
114 Гл. 2. Ассемблеры Рис. 2.25. Перемещение объектного кода во время исполнения программы. в каждой подпрограмме отдельным блоком. Для каждого под- программного блока ассемблер заводит отдельный набор счет- чиков (слов, размещений и позиций). Последнее необычное свойство COMPASS, которое мы об- судим, заключено в самом языке ассемблера. Для большинства ЭВМ код машинной операции однозначно определяется мнемо- ническим кодом исходного предложения. Для CYBER это не так. Рассмотрим предложения SAI А2 SAI В2 + ТАВ SAI ХЦ-В1 SAI XI-В1 Первое из них будет ассемблировано в 15-разрядную команду с машинным кодом операции 54; второе — в 30-разрядную команду с кодом операции 51; третье — в 15-разрядную коман- ду с кодом операции 53; четвертое недопустимо. При интерпретации мнемонических кодов, подобных SA1 (и большинства других кодов языка ассемблера CYBER), необхо- димо анализировать операнды. Подобные команды исходной программы известны как синтаксически определяемые команды, поскольку они распознаются по своему синтаксису (т. е. фор- ме предложения). Ассемблер COMPASS обрабатывает такие команды, просматривая мнемонический код операции и операн- ды и извлекая символьную строку, описывающую синтаксис. Для третьего из рассмотренных выше предложений синтаксиче- ский дескриптор будет иметь вид SAX-f-B. Этот дескриптор определяет команду группы А, операндом которой является
2.5. Примеры реализации 115 Строка Исходное предложение 1 IDENT 2 ОРТ! SA1 fl • fl КОД РАЗДЕЛА 1 3v ZR B4r$KIPl 4 SKIP1 SA6 я > « 5 EQ выход 6 BUFFER BSS 1000 БУФЕР ДЛИНОЙ 1000 СЛОВ 7 ORG BUFFER 8 LOG OPT1 ? 0РТ2 SA2 • я » / КОД РАЗДЕЛА 2 10 NZ a B3,SKIPS 11 SKIPS SA7 « и a 12 EQ ВЫХОД 13 LOC OPT! 14 ОРТЗ SA6 я я a КОД РАЗДЕЛА 3 15 EQ B1,B3,SKIP3 16 SKIP3 SA6 мая 17 EQ ВЫХОД 18 ORG BUFFER+1000 1? E&C *0 Я 20 END в Рис. 2.25в. сумма регистров X и В. Существуют 22 различных дескриптора подполей, которые могут комбинироваться для получения таких синтаксических дескрипторов. Синтаксически определяемые команды хранятся в таблице кодов операций в соответствии со своими синтаксическими де- скрипторами. Для каждого элемента в таблице дополнительно хранится информация, определяющая, как различные части исходного предложения должны размещаться в объектной про- грамме. При обработке предложений исходного языка ассемб- лер вначале ищет в таблице кодов операций мнемонический
116 Гл. 2. Ассемблеры код команды. Для некоторых команд такой поиск оказывается успешным. Однако для синтаксически определяемых команд, таких как SА1, он оканчивается безрезультатно. В этом случае ассемблер просматривает исходное предложение и извлекает из него (как было описано ранее) синтаксический дескриптор. За- тем выполняется второй просмотр таблицы. Если и этот поиск заканчивается неудачей, то опознание команды невозможно. Дополнительную информацию об ассемблере CYBER можно найти в CDC [1982а], Упражнения Раздел 2.1 1. Воспользуйтесь алгоритмом, приведенным на рис. 2.4, для исходной программы на рис. 2.1. Полученный вами результат должен быть таким, как показано на рис. 2.2 и 2.3. 2. Воспользуйтесь алгоритмом, приведенным на рис, 2.4, для следую- щей программы УУМ: SUM START 4000 FIRST LDX ZERO LDA ZERO LOOP ADD TABLE,X TIX COUNT JLT LOOP STA TOTAL RSUB TABLE RESW 2000 COUNT RESW 1 ZERO WORD 0 TOTAL RESW 1 END FIRST 3. Как уже отмечалось, для некоторых операций в алгоритме на рис. 2.4 не дано детального описания. (Например, операция поиска моди- фикатора, X в поле операндов.) Перечислите все, какие только можете, недетализированные операции и подумайте, как можно было бы их реали- зовать. 4. Алгоритм на рис. 2.4 реализован в виде единого блока. В то же время инженерный подход к созданию программного обеспечения требует, чтобы большие программы разбивались на несколько модулей с четко опре- деленными интерфейсами. Предложите набор модулей для реализации этого алгоритма. Укажите функции каждого из модулей и опишите, как каждый из них используется другими частями ассемблера. 5. Многие ассемблеры используют свободный формат записи команд. Метки должны начинаться в первом столбце исходного предложения, но другие поля (код операции, операнды, комментарии) могут начинаться в любом столбце. Друг от друга поля отделяются пробелами. Как надо из- менить алгоритм нашего ассемблера для того, чтобы он смог работать в свободном формате?
Упражнения 117 6. Алгоритм на рис. 2.4 позволяет найти некоторые из ошибок в ис- ходной программе. Однако существует много других ошибок, которые мо- гут возникать в процессе ассемблирования программ УУМ. Когда и как каждая из этих ошибок может быть обнаружена и какие действия должен при этом предпринять ассемблер? Раздел 2.2 1. Может ли ассемблер самостоятельно решить, какая из команд долж- на быть сгенерирована в расширенном формате? (Это позволит снять с программиста заботу о явном указании таких команд с помощью призна- ка Ч-.) 2. Как мы уже говорили, предложение BASE служит лишь для пере- дачи информации ассемблеру. Программист должен, кроме того, предусмот- реть команды наподобие LDB для загрузки соответствующего значения в базовый регистр. Может ли ассемблер автоматически генерировать коман- ду LDB по директиве BASE? Если да, то в чем заключаются плюсы и минусы подобной интерпретации предложения BASE? 3. Сгенерируйте объектный код для каждого предложения следующей программы УУМ/ДС: SUM START 0 FIRST LDX #0 LDA #0 + LDB #TABLE2 BASE TABLE2 LOOP ADD TABLE,X ADD TABLE2,X TIX COUNT JLT LOOP + STA RSUB TOTAL COUNT RESW 1 TABLE RESW 2000 TABLE2 RESW 2000 TOTAL RESW 1 END FIRST 4. Сгенерируйте законченную объектную программу для исходной про- 1раммы из упр. 3. 5. Модифицируйте алгоритм, показанный на рис. 2.4, так, чтобы он обрабатывал все допустимые в УУМ/ДС способы адресации. Как отра- зятся эти изменения на предложенной вами в упр. 2.1.4 модульной струк- туре? 6. Модифицируйте алгоритм, показанный на рис. 2.4, так, чтобы он позволял получать перемещаемые программы. Как отразятся эти изменения на предложенной вами в упр. 2.1.4 модульной структуре? 7. Формат записи-модификатора хорошо подходит для программ *УМ/ДС потому, что все адресные поля в командах и данных распола- гаются, начиная с границы полубайта. Как должна была бы выглядеть запись-модификатор, если бы это было бы не так (т. е. если бы адресные поля могли бы начинаться с произвольного места внутри байта и могли бы иметь произвольную длину)?
118 Гл. 2. Ассемблеры 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 и ВЕТА являются метками исход- ной программы. Объясните, чем различаются две следующие последователь- ности предложений: a) LDA ALPHA - ВЕТА б) LDA ALPHA SUB BETA 7. Чем различаются нижеприведенные последовательности предложений! a) LDA # 3 б) THREE EQU 3 LDA #THREE в) THREE EQU 3 LDA THREE
Упражнения 119 8. Модифицируйте алгоритм, показанный на рис. 24, так, чтобы он мог работать с несколькими программными блоками. 9. Модифицируйте алгоритм, показанный на рис. 24, так, чтобы он мог работать с несколькими управляющими секциями. 10. Предположим, что в ассемблере реализованы все возможности, опи- санные в разд. 2.3. Как следует в этом случае изменить таблицу имен по сравнению с тем, как она описана в разд. 2.1? 11. Если различные управляющие секции ассемблируются совместно, то некоторые ссылки между ними могут быть обработаны ассемблером (вме- сто того чтобы оставлять их загрузчику). Например, в программе, приве- денной на рис. 2.15, выражение в строке 190 может быть вычислено непо- средственно ассемблером, так как его таблица имен содержит всю необ- ходимую информацию. В чем заключаются преимущества и недостатки ракой обработки? 12. Пусть в программе, приведенной на рис. 2.11, мы использовали только два программных блока — непоименованный блок и CBLKS. Пред- положим, что элементы данных CDATA включены в непоименованный блок. Какие надо для этого сделать изменения в исходной программе? Покажите, какая в этом случае будет получена объектная программа, 13. Предположим, что метка LENGTH определена так, как в про- грамме, показанной на рис. 2.9. Чем различаются две нижеследующие по- следовательности предложений? a) LDA LENGTH SUB #1 б) LDA LENGTH—1 14. В соответствии с определениями имен на рис. 2.10 укажите значе- ния, тип и интуитивный смысл (если он есть) для каждого из следующих выражений: a) BUFFER-FIRST б) BUFFER + 4095 в) MAXLEN - 1 г) BUFFER + MAXLEN - 1 д) BUFFER-MAXLEN e) 2 * LENGTH ж) 2 * MAXLEN - 1 s) MAXLEN — BUFFER и) FIRST + BUFFER к) FIRST — BUFFER + BUFEND 15. В программе, приведенной на рис. 2.9, строка 107 записана как MAXLEN EQU BUFEND-BUFFER Нем это лучше записи вида MAXLEN EQU 4096 ion* Можно ли в программе, приведенной на рис. 2.15, заменить стро- ку 190 на MAXLEN EQU BUFEND-BUFFER
120 Гл. 2. Ассемблеры а строку 133 на + LDT # MAXLEN как мы это сделали на рис. 2.9? 17. Ассемблер мог бы просто предполагать, что все ссылки на имена, не определенные в данной управляющей секции, являются внешними ссылками. В этом случае отпадает необходимость в предложении EXTREF, Хорошо ли это? 18. Каким образом ассемблер, допускающий внешние ссылки, мог бы обойтись без предложения EXTDEF? Какие здесь есть преимущества и не- достатки? 19. Ассемблер мог бы автоматически использовать расширенный формат для команд, в операндах которых используются внешние ссылки. В этом случае программист мог бы не задавать в этих командах признак +• В чем заключаются преимущества и недостатки такого подхода? 20. В некоторых системах управляющие секции могут быть скомпоно- ваны из нескольких частей точно так же, как программные блоки. Какие проблемы ставит это перед ассемблером? Как они могут быть разрешены? 21. Предположим, что имена RDREC и COPY определены так, как по- дмазано на рис. 2.15. В соответствии с нашими правилами выражение RDREC—COPY является недопустимым (т. е. ассемблер и/или загрузчик откажутся его обрабатывать). Предположим, что в силу каких-либо причин программе действительно необходимо значение этого выражения. Как это можно сде- лать, не меняя правил составления выражений? 22. Мы рассмотрели большое количество директив ассемблера, и еще многие другие директивы могли бы быть реализованы в реальных ассембле- рах. Их поиск путем последовательного сравнения может оказаться весьма неэффективным. Как мы могли бы использовать таблицу, например похожую на ОРТАВ, для того, чтобы ускорить распознавание и обработку директив ассемблера? (Указание: ответ может зависеть от языка, на котором написан ассемблер.) 23. Что кроме листинга исходной программы со сгенерированным объект- ным кодом могло бы быть полезно для программиста? Предложите не- сколько вариантов листингов, которые могут быть сгенерированы, и рас- смотрите структуры данных или алгоритмы, необходимые для их построения. Раздел 2.4 1. Рассмотрите базовый ассемблер, алгоритм которого показан на рис. 2.4. Какие таблицы и подпрограммы следует включить в корневой сег- мент этого ассемблера? 2. Какие таблицы и подпрограммы следует включить в корневой сег- мент усовершенствованного ассемблера, в котором реализованы возможно- сти, описанные в разд. 2.3? 3. Процесс разрешения нескольких ссылок вперед должен требовать меньших накладных расходов, чем полный второй просмотр исходной про- граммы. Почему же для повышения эффективности все ассемблеры не ис- пользуют однопросмотровую схему ассемблирования? 4. Предположим, мы хотели бы, чтобы наш ассемблер выдавал таблицу перекрестных ссылок для всех имен, использованных в исходной программе. Для программы, приведенной на рис, f2.5> такой листинг мог бы иметь сле- дующий вид:
Упражнения 121 Имя Строка определения Строка использования 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?
Глава 3. Загрузчики и программы связывания Как мы видели, объектная программа содержит оттрансли- рованные команды и значения данных, полученные из исход- ной программы, и, кроме того, определяет адреса в оператив- ной памяти, куда эти команды и данные должны помещаться, В гл. 2 мы познакомились с тремя следующими процессами: 1. Загрузкой, обеспечивающей размещение программы в оперативной памяти для исполнения. 2. Перемещением, которое позволяет модифицировать объ- ектную программу так, что она может загружаться с адреса, отличного от первоначально заданного (см. разд. 2.2.2), 3. Связыванием, обеспечивающим объединение двух или бо- лее раздельно оттранслированных объектных программ и пре- доставляющим информацию, необходимую для разрешения ссы- лок между ними (см. разд. 2.3.5) , Загрузчик — это системная программа, выполняющая за- грузку. Многие загрузчики обеспечивают, кроме того, переме- щение и связывание. В некоторых системах функция связыва- ния отделена от функций перемещения и загрузки. Связывание выполняется специальной программой связывания (или редак* тором связей), перемещение и загрузка — загрузчиком. В боль- шинстве случаев трансляторы (т. е, ассемблеры и компилято- ры) создают в каждой конкретной системе объектный код в некотором стандартном формате. Таким образом, загрузчик и программа связывания могут использоваться вне зависимости от того, на каком языке была написана исходная программа, В этой главе мы изучим вопросы проектирования и реализа- ции загрузчиков и программ связывания. Для простоты мы ча- сто будем использовать термин «загрузчик» вместо «загрузчик и/или программа связывания». Ввиду того что процессы ас- семблирования и загрузки тесно связаны, данная глава по сво- ей структуре похожа на предыдущую. Многие из примеров, ис- пользованных нами при изучении ассемблеров, появятся и в этой главе. Во время обсуждения ассемблеров мы рассмотрели некоторые свойства и возможности, которые касаются как ас- семблера, так и загрузчика. В этой главе мы вновь встретимся с такими концепциями. Естественно, что теперь нас в основном будут интересовать функции загрузчика, однако важно помнить о тесной связи между трансляцией и загрузкой программ.
3.1. Основные функции загрузчика 123 Как и в предыдущей главе, мы вначале рассмотрим основ- ную функцию изучаемого программного обеспечения — в дан- ном случае загрузку объектной программы в оперативную па- мять для последующего ее исполнения. В разд. 3.1 представлен абсолютный загрузчик (absolute loader). Подобный загрузчик мог бы использоваться для УУМ совместно с ассемблером, описанным в разд. 2.1. В разд. 3.2 разбираются вопросы перемещения и связывания программ с точки зрения загрузчика. Мы обсудим несколько возможных способов представления объектной программы и ис- следуем, как они связаны со структурой ЭВМ. Мы также рас- смотрим связывающий загрузчик (linking loader), представляю- щий собой наиболее совершенный тип загрузчика, который ис- пользуется в большинстве современных вычислительных систем, В разд. 3.3 приведены некоторые из наиболее часто встречаю- щихся свойств загрузчика, которые не имеют прямой связи со структурой машины. Как и прежде, нашей целью является не рассмотрение всех возможных свойств, а изучение концепций и технических приемов, наиболее часто применяемых при созда- нии загрузчиков. В разд. 3.4 обсуждаются возможные альтернативные спосо- бы реализации функций загрузчика. Мы разберем различные способы выполнения перемещения и связывания, а также пре- имущества и недостатки каждого из них. С этой точки зрения будут рассмотрены редакторы связей (выполняющие связыва- ние до загрузки) и схема динамического связывания (в кото- рой связывание откладывается до момента исполнения про- граммы). Наконец, в разд. 3.5 мы вкратце обсудим некоторые приме- ры реальных загрузчиков и программ связывания. Мы рассмо- трим те же машины, для которых в разд. 2.5 были описаны ассемблеры, и укажем на взаимосвязь, существующую между ассемблером и загрузчиком. Как и ранее, мы сосредоточим наше внимание на тех аспектах системных компонент, которые зависят от особенностей аппаратуры или программного обеспе- чения. 3.1. Основные функции загрузчика В данном разделе мы обсудим наиболее важные функции загрузчика — запись объектной программы в оперативную па- мять и передачу управления на адрес начала ее исполнения. Вероятно, вы уже знакомы с тем, как выполняются эти функ- ции. Данный раздел будет служить отправной точкой для* по- следующего обсуждения более сложных функций загрузчика. Здесь мы рассмотрим абсолютный загрузчик, который может
124 Гл. 3. Загрузчики и программы связывания быть использован совместно с ассемблером, описанным в разд. 2.1. Для представления объектной программы будет ис- пользоваться формат, описанный в разд. 2.1.1. Пример объект- ной программы показан на рис. 3.1а. НдСОРТ д001 000д00 1 О 7 А Та001000л1Ел141033л482039л001036л281030л3010 15л48206 1лЗС1003л00102АОСЮ390010211 !ГОО1О1Ед1 5д0С1 036д48 2 06 1д08 1033д4С0000д4 54г4бд0000 03 000000 Л 1Л°02039Л1ЕЛ041030Л0°1 ° 30лЕ°205Вл3020ЗЕлВ8 205Пл281030^30205 7д54 9О39л2С2О5Ел38203е ТЛ0°2057Л1СЛ101036Л4С0000ЛЕ1Л001000Л041030Е0207 9Л302064Л509039ВС20Л79 2С1036 £002073073820644С000005 Л /ч л л л J^OQIOOO Адрес Содержимое 0000 хххххххк хххххххх хххххххх хххххххх оою хххххххх хххххххх хххххххх хххххххх • о • • • OFFO хххххххх хххххххх хххххххх хххххххх 1000 14103348 20390010 36281030 30101548 1010 20613С10 0300102А 0С103900 102D0C10 1020 36482061 0810334С 0000454F 46000003 1030 000000ХХ хххххххх хххххххх ХХХХХХХХ 4-C0PY • • • • • 2030 хххххххх хххххххх хх041030 001030Е0 2040 205D3020 3FD8205D 28103030 20575490 2050 392С205Е 38203F10 10364С00 00F10010 2060 00041030 Е0207930 20645090 39DC2079 2070 2С103638 20644С00 0005[хххх хххххххх 2080 хххххххх хххххххх хххххххх хххххххх « • • « * 6 Рис, 3.1. Загрузка абсолютной программы, Поскольку от нашего загрузчика не требуется выполнения связывания и перемещения программ, его' работа весьма про- ста. Все выполняется за один просмотр. Вначале, для того чтобы удостовериться, что программа, переданная для загруз- ки, корректна (и что для нее достаточно места в оперативной памяти), просматривается запись-заголовок. Затем последова- тельно считываются записи тела программы и содержащийся в них объектный код помещается в оперативную память по ука- занному адресу. И наконец, как только будет прочитана за- пись-конец, загрузчик передает управление по адресу, задан- ному в качестве адреса начала исполнения программы, На
3.1. Основные функции загрузчика 125 рис. 3.16 показано, как будет выглядеть программа рис. 3.1а после загрузки. Содержимое областей оперативной памяти, для которых нет записей в теле программы, обозначено как хххх. Это означает, что первоначальное содержимое этих областей памяти не менялось. На рис. 3.2 показан алгоритм рассмотренного нами простого загрузчика. Хотя данный процесс крайне прост, все же есть один аспект, заслуживающий комментария. В нашей объект- ной программе каждый байт ассемблированного кода дается в begirt прочитать запись-заголоеок проверить имя и длину программы прочитать первую запись тела программы while тип записи О 'Е/ do begirt {если объектный код задан в символьном виде, то преобразовать его во внутреннее представление? переписать объектный код в заданное место оперативной памяти прочитать следующую запись объектной программы end передать управление по адресу, заданному в записе-конец end Рис. 3.2. Алгоритм абсолютного загрузчика. шестнадцатеричном символьном представлении. Например, ма- шинный код операции для команды STL представляется в виде пары символов 14. Когда они считываются загрузчиком (как часть объектной программы), они будут занима'гь два байта памяти. Однако в команде, которая загружается для выполне- ния, этот код операции должен быть записан в одном байте с шестнадцатеричным значением 14. Таким образом, каждая пара байтов объектной программы должна быть упакована во время загрузки в один байт. Очень важно четко понимать, что на рис. 3.1а каждый символ представляет один байт записи объ- ектной программы. С другой стороны, на рис. 3.16 каждый символ представляет одну шестнадцатеричную цифру (т. е, пол- байта). Данный способ представления объектной программы не- эффективен как с точки зрения занимаемого объема памяти, так и времени выполнения загрузки. Поэтому в большинстве машин объектные программы хранятся в двоичном представле- нии, в котором каждый байт объектного кода записывается в виде отдельного байта объектной программы. Конечно, в таком представлении каждый байт может содержать любую двоичную величину. Мы должны быть уверены, что в имеющихся в п Напомним, что под объектной программой понимается информация, поступакицая загрузчику в качестве исходных данных, — Прим, ред.
126 Гл. 3. Загрузчики и программы связывании системе соглашениях по работе с файлами и устройствами не предусмотрено использование каких-либо кодов в качестве уп- равляющих символов. Например, соглашение, описанное в разд. 2.1, в котором в качестве признака конца записи исполь- зуется байт, содержащий шестнадцатеричное значение 00, оче- видно, не подходит для представления объектной программы в двоичном виде. Очевидно, что объектная программа, хранимая в двоичном виде, не годится для использования в книге, так как ее трудно воспринимать читателю. Поэтому мы и далее будем пользо- ваться в наших примерах символьным представлением объект- ных программ. 3.2. Машинно-зависимые свойства загрузчиков Абсолютный загрузчик, описанный в разд. 2.1, конечно, весьма прост и эффективен, однако данная схема имеет ряд потенциальных недостатков. Одним из наиболее очевидных яв- ляется то, что от программиста требуется определять фактиче- ский адрес начала загрузки программы во время ее ассембли- рования. Если мы рассматриваем очень простую ЭВМ с небольшим объемом оперативной памяти (такую, как стандарт- ная модель УУМ), то это не создает особых проблем. В этом случае места достаточно только для выполнения одной про- граммы, и поэтому начальный адрес загрузки известен заранее. Для более совершенных машин, обеспечивающих работу с большим объемом оперативной памяти (подобных УУМ/ДС), ситуация не столь проста. Очень часто желательно одновремен- но выполнять несколько независимых программ, совместно использующих оперативную память и другие ресурсы. Это озна- чает, что мы не знаем заранее, куда будет загружена програм- ма. Поэтому для того, чтобы обеспечить эффективное разделе- ние ресурсов машины, требуется создавать перемещаемые, а не абсолютные программы. Абсолютные программы неудобны также и для использова- ния в библиотеках стандартных подпрограмм. Количество под- программ, содержащееся в большинстве таких библиотек (на- пример, пакеты для научных или математических расчетов), намного больше, чем требуется для каждой отдельно взятой программы. Поэтому для того, чтобы эффективно использовать оперативную память, необходимо иметь возможность выбирать только те подпрограммы, которые действительно нужны. Сде- лать это, если все подпрограммы должны загружаться с зара- нее назначенных адресов, весьма непросто. В этом разделе мы рассмотрим более сложный загрузчик. Такой загрузчик вполне подходит для УУМ/ДС и является
3.2. Машинно-зависимые свойства загрузчиков 127 типичным для большинства современных ЭВМ. Наряду с про- стой функцией размещения программы в оперативной памяти, описанной в предыдущем разделе, данный загрузчик выполняет, также перемещение и связывание программ. Одним из вопро- сов, которого мы коснемся в данном разделе, будет вопрос о влиянии структуры машины на решения, применяемые при про- ектировании загрузчика. Необходимость выполнения перемещения программ косвенно связана с переходом на более мощные и большие ЭВМ. Кон- кретный способ, используемый загрузчиком для реализации пе- ремещения, зависит от структуры ЭВМ. Эта зависимость будет рассмотрена в разд. 3.2.1. Мы обсудим различные технические приемы, которые могут быть использованы для перемещения программ, и укажем область их применения. В разд. 3.2.2 разбирается процесс связывания программ с точки зрения загрузчика. Функция связывания по сравнению с перемещением обладает меньшей степенью машинной зависимо- сти. Однако на практике для реализации обеих этих функций очень часто используется один и тот же механизм. Кроме того, процесс связывания обычно требует перемещения некоторых подпрограмм. (См. приведенный выше пример использования библиотечных подпрограмм.). Поэтому мы будем рассматривать эти два процесса/вместе. В разд. 3.2.3 обсуждаются структуры данных и алгоритмы, используемые в типичном связывающем (и перемещающем) за- грузчике. Рассматриваемые здесь алгоритмы будут использова- ны в последующих разделах в качестве отправной точки при изучении более сложных возможностей, предоставляемых за- грузчиком. 3.2.1. Перемещение Загрузчики, обеспечивающие перемещение программ, назы- ваются перемещающими загрузчиками {relocating loaders) или относительными загрузчиками (relative loaders). С концепцией перемещения программ мы познакомились в разд. 2.2.2. Вам, прежде чем продолжить чтение, следует бегло повторить мате- риал этого раздела. В данном разделе мы обсудим два спосо- ба, в которых информация о перемещении задается как состав- ная часть объектной программы. Первый из рассматриваемых способов в основном аналоги- чен тому, что мы обсуждали в гл. 2. Для описания каждого фрагмента объектного кода, требующего изменения при переме- щении, используется специальная запись-модификатор. (Ее фор- vvM?niICaH в Разд‘ 2.3.5.) На рис. 3.3 показана программа для УУМ/ДС, которую мы будем использовать для иллюстрации первого способа задания информации о перемещении. Это та же
128 Гл. 3. Загрузчики и программы связывания Строка Адрес Исходное предложений Объектный код 0000 COPY START 0 3L0 0000 FIRST STL RETADR 17202Й 12 0003 LDB ♦LENGTH 692020 13 BASE LENGTH 15 0006 CLOOP 4 JSUB RDREC 4B101036 20 000А LDA LENGTH 032026 25 0000 COHP ♦0 290000 30 0010 JEQ ENDFIL 332007 35 0013 +JSUB WRREC 4B10105D 40 0017 J CLOOP 3F2FEC 45 001А ENDFIL LDA EOF 032010 50 001D STA BUFFER 0F2016 55 0020 LDA ♦3 010003 60 0023 STA LENGTH 0F200D 65 0026 4 JSUB WRREC 4В10105Й 70 002А J ©RETADR 3E2003 80 002D EOF BYTE C'EOF' 454F46 95 0030 RETADR RESW 100 0033 LENGTH RESU 1 105 0036 BUFFER RESB 4096 110 115 « ПОДПРОГРАММА ВВОДА ЗАПИСИ НА ВУФЕР 120 125 1036 RDREC CLEAR X B410 130 1038 CLEAR A B400 132 103А CLEAR S B440 133 103С +LDT ♦4096 75101000 135 1040 RLOOP TD INPUT E32019 140 1043 JEQ RLOOP 332FFA 145 1046 RD INPUT. BB2013 150 1049 COMPR A,S . A004 155 104В JEQ EXIT 332008 160 104Е STCH BUFFERД 57C003 165 1051 TIXR T B850 170 1053 JLT RLOOP 3B2FEA 175 1056 EXIT STX LENGTH 134000 180 1059 RSUB 4F0000 185 105С INPUT BYTE X'Fl' Fl 195 200 ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 210 1050 WRREC CLEAR X B410 212 105F LDT LENGTH 774000 215 1062 ULOOP TD OUTPUT E32011 220 1065 JEQ WLOOP 332FFA 225 1068 LDCH BUFFERS 53C003 230 106В UD OUTPUT DF2008 235 106Е TIXR T B850 240 1070 JLT WLOOP 3B2FEF 245 1073 RSUB 4F0000 250 1076 OUTPUT BYTE X'05' 05 255 END FIRST Рис. 3.3. Пример программы для УУМ/ДС (с рис. 2.6), самая программа, что и на рис. 2.6. Здесь мы ее воспроизво- дим исключительно для удобства. Большинство команд данной программы использует относительную или непосредственную адресацию. Единственной частью объектной программы, содер*
3.2. Машинно-зависимые свойства загрузчиков 129 жащей фактические адреса, являются команды, в которых ис- пользуется расширенный командный формат (строки 15, 35 и 65). Поэтому при перемещении программы изменение значений потребуется только для этих величин. На рис. 3.4 показана объектная программа, соответствую- щая исходному тексту на рис. 3.3. Обратите внимание, что для каждого значения, требующего изменения при перемещении, имеется отдельная запись-модификатор (в данном случае это три ранее упомянутые команды). Каждая запись-модификатор определяет начальный адрес и длину поля, значение которого HCOPY ^)00000д0 01077 TOOOOOO^l 1^1.72021)6 9202D4B 101036д032 0 262 9000(^332 0074B10105D3F2FEC.P 32010 7д0 0 0 0 1 D1 30 F 2 016д0 10 0 0 Зд0 F 2 0 0 0д4 В1 010 5 Пд3 Е 2 0 0 Зд4 5 4 F 4 6 Т 0 0 10 3 6д1 ПДВ 4 10дВ 4 0 0дВ 4 4 0д7 5 10 1 0 0 0дЕ 3 2 0 19Д3 3 2 F F АДО В 2 01 ЗдА 0 0 4д3 3 2 0 0 8д5 7 С 0 0 ЗдВ 8 5 О 70010531D3B2FEA134OOO4FO00OF1B410774000E3201L332FFA53C003DF2008B850 л . л л л /\. л л л л л л л Д ТОО 107007ЗВ2FEF4F0 00005 л лл А А ИдООООО 7ДО5+СОРУ МдОООО 14дО5 + СОРУ М00002 7.05+COPY А Л EOOOQOO Л 4 Рис. 3.4. Объектная программа, в которой перемещение задается с помощью записей-модификаторов. необходимо изменить, а также тип требуемой модификации. В данном примере все модификации заключаются в прибавле- нии значения метки COPY, представляющей собой начальный адрес программы. Алгоритм, используемый загрузчиком для вы- полнения данной операции, описывается в разд. 3.2.3. Дополни- тельные примеры определяемого подобным образом перемеще- ния будут приведены в следующем разделе при изучении вза- имосвязи между перемещением и связыванием. Записи-модификаторы являются удобным средством для за- дания информации о перемещении программы. Однако данная схема годится не для всех машин. Рассмотрим, например, про- грамму, приведенную на рис. 3.5. Это перемещаемая программа, написанная для стандартной модели УУМ. Ее важным отличи- ем от программы, изображенной на рис. 3.3, является то, что стандартная модель УУМ не имеет относительной адресации. В данной программе адреса всех команд, за исключением RSUB, должны модифицироваться при перемещении. Поэтому для задания информации о перемещении необходимо иметь 31 запись-модификатор, что потребует более чем двукратного уве- личения объема памяти, занимаемой объектной программой, по сравнению с программой, показанной на рис. 3.4. 5 Зак. 792
130 Гл. 3. Загрузчики и программы связывания Строка Адрес Исходное предложение 0616КТНМЙ КОД 5 0000 COPY START 0 10 0000 FIRST STL RETADR 140033 15 0003 CLOOP JSUB RDREC 481039 20 0006 LDA LENGTH 000036 25 0009 COMP ZERO 280030 30 000С JEQ ENDFIL 300015 35 000F JSUB WRREC 481061 40 0012 J CLOOP 3C0003 45 0015 ENDFJL LDA EOF 00002A 50 0018 STA BUFFER 0C0039 55 001В LDA THREE 00002П 40 001Е STA LENGTH 40C0036 65 0021 JSUB WRREC 481061 70 0024 LDL RETADR 080033 75 0027 RSUB 4C0000 80 002А EOF BYTE C'EOF' 454F46 85 002» THREE UORB 3 000003 90 0030 ZERO WORD 0 000000 95 0033 RETADR RESU 1 100 0036 LENGTH RESW 1 105 0039 BUFFER RESB 4096 310 * 315 9 ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР 120 325 1039 RDREC LDX ZERO 040030 130 103С LDA ZERO 000030 135 103F RLOOP ТВ INPUT E0105D 140 1042 JEQ RLOOP 30103F 145 1045 RD INPUT B8105B 150 1048 COMP ZERO 280030 155 1048 JEQ EXIT 301057 160 104Е STCH BUFFERrX 548039 165 1051 TIX MAXLEN 2C105E 170 1054 JLT RLOOP 38103F 175 1057 EXIT STX LENGTH 100036 180 105А RSUB 4C0000 185 105» INPUT BYTE X'Fl' Fl 190 105Е NAXLEN UORB 4096 001000 195 200 » ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА 205 210 1061 URREC LDX ZERO 040030 215 1064 ULOOP TD OUTPUT E01079 220 1067 JEQ WLOOP 301064 225 106А LDCH BUFF ERrX 508039 230 106» UD OUTPUT DC1079 235 1070 TIX LENGTH 2C003& 240 1073 JLT WLOOP 381064 245 1076 RSUB 4C0000 250 1079 OUTPUT BYTE X,05/ 05 255 END FIRST Рис. 3.5. Перемещаемая программа для стандартной модели УУМ. На машинах, где преимущественно используются прямая ад- ресация и фиксированный командный формат^ очень часто бо- лее эффективным оказывается другой способ задания информа- ции о перемещении. На рис. 3.6 показано, как этот способ мо- жет быть применен к нашему примеру. В данном случае запи-
Зд2. Машинно-зависимые свойства загрузчиков 131 си-модификаторы не используются. Формат записей тела про- граммы тот же, что и ранее, за исключением того, что теперь с каждым словом объектного кода связан разряд перемещения. Поскольку все команды УУМ занимают одно слово, это озна- чает, что для каждой потенциальной команды имеется один разряд перемещения. Эти разряды собираются вместе и обра- зуют маску, которая записывается после указателя длины каж- дой записи тела программы. На рис. 3.6 эта маска (в символь- ном виде) показана в виде трех шестнадцатеричных цифр (на рисунке они подчеркнуты). Если разряд перемещения установлен в 1, то при перемеще- нии программы начальный адрес программы добавляется к сло- ву, соответствующему этому разряду. Значение разряда пере- мещения, равное 0, показывает, что никаких преобразований Л000000Л00107А ^оооооо^Ец^иоозздвкз^оооозб^воозо^ооов^вюбисоооз^оооаА^сооз^оооога ТлО00О1Ел15,ЕООрс0О36л4810б1лО8ООЗЗл4СООООл454г46лОО0ОО^0О000а T00I039 1EFFCO4OO3OOOO03OaE0105Da301O3FaD81G5Da280O3O.3010575480392C105E38103F Л Л А—-А ллллллллл T.O01057.OA.8OO.10OO36.4COOOO.F1.O01OQQ 3'00106119FE0040030E01079301064508039.DCI0792C00363810644C000005 Л Л ‘Л-----А ЛАДДА АЛЛ EO000QQ Л Рис. 3.6. Объектная программа, в которой перемещение задается с помощью маски перемещения. при перемещении делать не надо. Если запись тела программы содержит менее 12 слов объектного кода, то разряды, соответ- ствующие неиспользуемым словам, устанавливаются в 0. Таким образом, в первой записи тела программы маска FFC (пред- ставляющая собой строку 111111111100) показывает, что все 10 слов объектного кода должны быть модифицированы при перемещении программы. В данных словах находятся команды, соответствующие в исходной программе строкам с 10 по 55 (см. рис. 3.5). Маска Е00 во второй записи тела программы опре- деляет, что модификация требуется для трех первых слов. Оставшаяся часть объектного кода в данной записи представ- ляет собой константы (и команду RSUB), не требующие моди- фикации. Аналогично строятся остальные записи тела программы. Об- ратите внимание, что объектный код, генерируемый для коман- ды LDX в строке 210, начинает новую запись тела программы, хотя в предыдущей записи достаточно места для его размеще- ния. Это произошло потому, что каждый разряд перемещения связан с 3-байтовым сегментом объектного кода (словом) в записи тела программы. Поэтому каждое значение, требующее модификации при перемещении, должно совпадать с одним из этих 3-байтовых сегментов для того, чтобы ему можно была Б*
132 Гл. 3. Загрузчики и программы связывания поставить в соответствие разряд перемещения. Ассемблирован- ная команда LDX действительно требует модификации, посколь- ку в ней используется прямая адресация. Однако, если бы она была помещена в предыдущую запись тела программы, она не была бы правильно выравнена для соответствия разряду пере- мещения, так как в строке 185 была сгенерирована 1-байтовая константа. Поэтому данная команда должна начинать в объект- ной программе новую запись тела программы. Вам следует тщательно изучить оставшуюся часть объектной программы на рис. 3.6 и разобраться с тем, как ассемблер ге- нерирует разряды перемещения и как они используются загруз- чиком. Некоторые ЭВМ предоставляют аппаратные средства, облег- чающие организацию перемещения программ. Например, в не- которых машинах все ссылки к оперативной памяти рассмат- риваются как относительные ссылки с базовым адресом, назна- чаемым пользователем. Преобразование этих относительных адресов в физические производится во время исполнения про- граммы. (Мы обсудим этот процесс позднее — при разборе управления оперативной памятью в гл. 6.) Однако, как мы увидим в следующем разделе, загрузчик тем не менее должен выполнять некоторые функции перемещения подпрограмм для обеспечения их связывания. 3.2.2. Связывание программ С основными концепциями, затрагивающими связывание программ, мы познакомились в разд. 2.3.5. Прежде чем про- должить чтение, вам следует повторить материал этого раздела и еще раз посмотреть приведенные там примеры. В данном разделе мы обсудим более сложные примеры внешних ссылок между программами и изучим взаимосвязь между перемещени- ем и связыванием. В следующем разделе дается описание ал- горитма загрузчика, обеспечивающего связывание и перемеще- ние программ. На рис. 2.15 в разд. 2.3.5 была показана программа, состоя- щая из трех управляющих секций. Эти управляющие секции могут ассемблироваться как вместе (т. е. за одно обращение к ассемблеру), так и независимо друг от друга. В любом случае после ассемблирования они будут представлены в виде отдель- ных сегментов объектного кода (рис. 2.17). Однако програм- мист, естественно, склонен рассматривать программу в виде логического целого, включающего в себя все связанные управ- ляющие секции. Однако с точки зрения загрузчика никакой программы нет вообще. Имеются только управляющие секции, которые должны связываться, перемещаться и загружаться. За- грузчик никоим образом не может узнать (да это ему и не
3.2, Машинно-зависимые свойства загрузчиков 133 нужно) о том, какие управляющие секции ассемблировались одновременно. Рассмотрим три (раздельно ассемблированные) программы, изображенные на рис. 3.7. Каждая из них состоит из единствен- ной управляющей секции и содержит списки (LISTA, LISTB, LISTC). Концы этих списков помечены метками ENDA, ENDB, ENDC. Метки в начале и в конце списков являются внешними именами (т. е. они доступны для связывания). Обратите вни- мание, что каждая программа содержит один и тот же набор ссылок к этим внешним именам. Три из них — операнды ко- манд (предложения с метками REF1— REF3), а остальные — ссылки на элементы данных длиной в одно слово (REF4— REF8). На данном примере мы посмотрим, в чем состоят раз- личия в обработке этих идентичных выражений в указанных трех программах, и подчеркнем взаимосвязь между перемеще- нием и связыванием. Для того чтобы сосредоточиться на дан- ном вопросе, мы сознательно не стали приводить реальные про- граммы. Те части программ, которые не используются в про- цессах перемещения и связывания, опущены. То же самое относится и к сгенерированному для этих программ объектному коду (см. рис. 3.8). Рассмотрим вначале предложение с'меткой REF1. Для пер- вой программы (PROGA) REF1 является просто ссылкой на метку внутри данной программы. Она ассемблируется обычным образом как команда, использующая адресацию относительно счетчика команд. Никаких модификаций для перемещения или связывания не требуется. В PROGB, с другой стороны, тот же самый операнд ссылается на внешнее имя. Ассемблер исполь- зует для этой команды расширенный командный формат и за- носит в адресное поле значение 000000. Соответствующая ей объектная программа (см. рис. 3.8) содержит запись-модифи- катор, в котором загрузчику предписывается добавить значение имени LISTA к данному адресному полю в момент связывания программы. Совершенно аналогично эта ссылка обрабатывается для PROGC. ^Похожим образом обрабатывается ссылка в команде с мет- кой REF2. В PROGA выражение, используемое в качестве опе- ранда, представляет собой сумму внешней ссылки и константы. Ассемблер запоминает величину константы в адресном поле команды, а запись-модификатор предписывает загрузчику доба- вить к данному полю значение LISTB. В PROGB то же самое выражение является локальной ссылкой и поэтому обрабаты- вается обычным образом с использованием адресации относи- тельно счетчика команд. Ни перемещения, ни связывания здесь не требуется. В команде с меткой REF3 используется непосредственный операнд, значением которого является разность между ENDA
134 Гл. 3. Загрузчики и программы связывания Объектный код Адрес Исходное предложение FROGA START 0 EXTDEF LISTA,ENBA EXTREF LISTBrENDBrUISTCrENDC REFI LDA LISTA 03201D «023 REF2 +LDT LISTB+4 77100004 0027 REFS LDX 4ENDA-LISTA 050014 0040 EISTA EQU * 0054 ENDA EQU # 0054 REF4 WORD ENDA-LISTA+LISTC 000014 0057 REF5 WORD ENDC-LISTC-10 FFFFF6 005A REF6 WORD ENDC-LI5TC+LISTA-1 00003F 005D REF7 WORD ENDA-LISTA-(ENDB-LISTB) 000014 0060 REFS WORD LISTB-LISTA FFFFC0 END REFI Адрес Исходное предложение Объектный код 0000 PROGR START 0 EXTDEF LISTB.ENDB EXTREF LISTA,ENDA,LISTC,EWC «036 REFI +LDA LISTA 03100000 003A REF2 LDT LISTB+4 772027 003D REF3 +LDX ♦ENDA-LISTA 05100000 • 0060 LISTB EQU * 0070 ENDB EQU 0070 REF4 WORI? ENDA-LISTA+LISTC 000000 0073 REF5 WORD ENDC-LISTC-10 FFFFF6 0076 REF6 WORD ENDC-LISTC+LISTA-1 FFFFFF 0079 REF7 WORD ENDA-LISTA-(ENDB-LISTB) FFFFF0 007C REFS WORD USTB-LISTA 000060 END Рис. 3.7, Модельная программа, иллюстрирующая связывание и перемещение.
3.2. Машинно-зависимые свойства загрузчиков 135 Адрес Исходное предложение Объектный код 0000 PROGC START 9 EXTDEF LISTC, ENtiC EXTREF LISTA,ENDA,LISTS,ENDB 0013 REFi +LDA LISTA 03100000 001C REF2 +LDT LISTB+4 77100004* 0020 REF3 +LM 0ENDA4.ISTA 05100000 0030 USTC EQU * 0042 ENIOC EQU * 0042 REF4 UORB EMJA-LISTA+LISTC’ 000030 0045 REF5 UORB ENDC-LISTC-10 ЛААААЛ- VWvWS 0048 REF6 UORB ENDC-LISTC+LISTA-1 000011 004B REF 7 UORB ENDA-LISTA-(ENDB-LISTEi> 000000 004E REF8 WORD LISTB-LISTA 000000 END Рис. 3.7. Продолжение. и LISTA (т. е. длина списка в байтах). В PROGA ассемблер располагает всей необходимой информацией для вычисления этого значения. Однако при ассемблировании PROGB (и PROGC) значения меток неизвестны. В этих программах выра- жение должно вычисляться как внешняя ссылка (с двумя записями-модификаторами) даже несмотря на то, что в конеч- ном счете результат является абсолютной величиной, не зави- сящей от места загрузки программы. Оставшиеся ссылки иллюстрируют другие возможные вари- анты. Общий подход, принятый при ассемблировании, заключа- ется в том, что ассемблер стремится вычислить как можно большую часть выражения. Оставшиеся термы передаются за- грузчику с помощью записей-модификаторов. Для того чтобы разобраться в этом, рассмотрим предложение с меткой REF4. В PROGA ассемблер может вычислить все выражение, за иск- лючением значения LISTC. В результате имеем начальное зна- чение 000014 (шестнадцатеричное) и одну запись-модификатор. Однако в PROGB то же самое выражение не содержит ни од- ного терма, который может быть вычислен ассемблером. По- этому объектный код будет содержать начальное значение, рав- ное 000000, и три записи-модификатора. В PROGC ассемблер знает относительное значение LISTC (но не фактический ад- рес, который неизвестен до тех пор, пока программа не будет загружена в память). Начальное значение данной константы будет содержать относительный адрес LISTC (шестнадцатерич- ная величина 000030). Записи-модификаторы содержат указа- ния загрузчику прибавить начальный адрес программы (т. е.
135 Гл. 3. Загрузчики и программы связывания HFROCa Л00000л000063 tyaSTA /)00040ENDA JD00054 iQ-ISTB ENDS LISTC ENDC ^000020ЛОАЛ0.3201 Пл77100004J350014 T О О О 0 5 4 0 f О О О 014 F J’ F f V 6 0 0 Q v p / и О 0 014 F f f f c 0 HP0002<0 5Л+Ы STB л л л $00O54AO6+LISTC N/)OO05 7AO6A+ENDG ЙЛОООО5 7aO^-LISTC $0005^0 6a+ENDC ВЛ0000 5АЛ0 6Л-Ы STC МлОО О 0 5 АДО 6+p RO ОД >0OOO5daO6a-£NDB. Ha00005Da06+LISTB M/p0006Q.06A+LX5TB 3HAO0006OO6-PKQGA ££0002 Cr дХко g jT£b ooo оло qoo7f fyblSTB a000060ENDB .000070 ли$ТС £NDC M ГлО°0036лОвл03100000л77202^05100000 r^uuu-j /aU->atLISTA $0003Ea05.+ENDA >U>O003EA05-LXSTA $0(J070a06a+ENDA $00070a06“LXSTA ^рооо7олобл+Ы5та ИРООО73ЛО6Л+ЕШ)С ИРООО74Р6Л+11ВТС $00076AO6A+ENbO >WQ0076a06a-I.ISTC Эф0007бАО6А+Ы5ТЛ IW0007A06.+ENDA U^00079a06-ETSTA $0007CA06.+PROGB $0007слОбл-ШТА £ mocc £00000000052 a000030.ENJ)0 000042 ! ®ЛОООО18ЛОСЛО310ООООЛ771ОООО4ЛОЯООООО Koooflp^usT^00008^0001^00™^00000 $0ООХЬ£5£ХШ& ЭД000рАО5А+Ет $000*10$. EXSTA ^ООООА&б^Ет ЭД0004$Об-ШТА $0004^0 6+PR0GQ $00048л0б+ХХ$ТД $0004B06+ENDA эдооо^обх-ттд $0004Ba0$ENDB $0004Ba06+LISTK $0004$0$-LISTB ^00004^О6лШТа значение PROGC), приба- вить значение ENDA и вы- честь значение LISTA. Та- ким образом, выражение REF4 представляет собой простую внешнюю ссылку в случае PROGA, более сложную внешнюю ссылку в случае PROGB и комби- нацию перемещения с внеш- ней ссылкой в случае PROGC. Вам следует самостоя- тельно разобраться с остав- шимися ссылками (с REF5 по REF8) и убедиться, что вы правильно поняли, как были сгенерированы объ- ектный код и записи-мо- дификаторы, показанные на рис. 3.8. На рис. 3.9а приведены три рассмотренные нами программы после загрузки и связывания. Программа PROGA была загружена, начиная с адреса 4000. Не- посредственно после нее были загружены PROGB и PROGC. Заметим, что в ре- зультате (после перемеще- ния и связывания) каждое выражение с REF4 по REF8 будет иметь одно и то же значение во всех трех программах. Так и должно было быть, поскольку в ис- ходных программах они имели один и тот же вид. Рассмотрим, например, значение метки REF4 в PROGA. Она расположена по адресу 4054 (этот адрес Рис. 3.8. Объектная программа, соответствующая рис, 3.7,
3.2. Машинно-зависимые свойства загрузчиков 137 Адрес Содержимое 0000 хххххххх хххххххх хххххххх хххххххх 3FF0 4000 4010 4020 4030 4040 4050 4060 4070 4 080 4090 40АО 40В0 40С0 40D0 40ЕО 40F0 4100 4 110 4120 4130 4140 хххххххх 03201D77 000083f 05100014 008 1040С705 0014 00412600 00080040 00 41260000 40С70510 0014 51000004 031040 40772027 4-PROGA 4—PROGB 08004051 00000400 0310 40407710 <-* PROGC .......... 00412600 00080040 51000004 000083|хх хххххххх хххххххх хххххххх хххххххх хххххххх хххххххх хххххххх Рис. 3.9а. Программа рис. 3.8 после связывания и загрузки. 3.96. Выполнение операций связывания и перемещения для предложения REF4 из PROGA,
138 Гл. 3, Загрузчики и программы связывания вычисляется как сумма начального адреса PROGA и относительного адреса REF4, равного 0054). На рис. 3.96 детально показан процесс вычисления этого значения. Начальное значение (полученное из записи тела про- граммы) равно 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. Машинно-зависимые свойства загрузчиков 139 вы поняли, какие именно вычисления надо делать, и могли вы- полнить их вручную (следуя яри этом инструкциям, содержа- щимся в объектных программах). 3.2.3. Таблицы и алгоритмы связывающего загрузчика Теперь мы готовы к тому, чтобы рассмотреть алгоритм ра- боты связывающего (и перемещающего) загрузчика. Мы будем использовать записи-модификаторы таким образом, чтобы функ- ции связывания и перемещения выполнялись с помощью одного и того же механизма. Как уже упоминалось, данный тип за- грузчиков часто применяется для машин (подобных УУМ/ДС), которые используют относительную адресацию и тем самым де- лают перемещение большинства адресов ненужным. Алгоритм связывающего загрузчика значительно более сло- жен, чем алгоритм абсолютного загрузчика, который мы рас- сматривали в разд. 3.1. Входная информация для этого загруз- чика состоит из набора объектных программ (т. е. управляю- щих секций), которые должны быть связаны друг с другом. В управляющих секциях могут использоваться (и это обычная ситуация) внешние ссылки на имена, значения которых во входном потоке еще не были определены. В этом случае тре- буемое связывание не может быть выполнено до тех пор, пока не будут назначены адреса для всех требуемых имен (т. е. до тех пор, пока не будет прочитана требуемая управляющая сек- ция). Поэтому связывающий загрузчик обычно выполняет два просмотра входного потока точно так. же, как это делает ас- семблер. В общих чертах эти два просмотра, выполняемые связывающим загрузчиком, имеют много общего с двумя про- смотрами, выполняемыми ассемблером: во время первого про- смотра назначаются адреса для всех внешних ссылок, а во время второго выполняются фактическая загрузка, перемеще- ние и связывание. Основная структура данных, необходимая для связывающего загрузчика,— это таблица внешних имен (ESTAB). Данная таблица является аналогом SYMTAB ассемблера и использует- ся для хранения имен и адресов всех внешних ссылок для все- го набора управляющих секций, загружаемых совместно. Очень часто в этой таблице также запоминается информация о том, какая управляющая секция содержит определение имени. Обыч- но ESTAB организуется в виде хеш-таблицы. Двумя другими важными переменными являются PROGADDR (адрес загрузки программы) и CSADDR (адрес управляющей секции). Перемен- ная PROGADDR — это адрес начала программы в оперативной памяти, куда должна загружаться связываемая программа. Этот
140 Гл* 3. Загрузчики и программы связывания Pass U begirt получить PROGADDR от операционной системы занести в CSADDR значение PROGADDR {для первой управляющей секции} while не конец входного потока da begin прочитать следующую входную запись {запись-заголовок управляющей секции} занести в CSLTH длину управляющей секции поиск имени управляющей секции в ESTAB if нашли then занести признак ошибки (дважды определенное внешнее имя) else занести имя управляющей секции в ESTAB со значением CSADDR while тип записи О 'Е' do begin прочитать следующую входную запись if тип записи « 'D' then for для каждого имени записи do begin поиск имени в ESTAB if нашли then занести признак ошибки (дважды определенное внешнее имя) else занести имя в ESTAB со значением (CSADDR + указанный адрес) end (fori end {while <> 'E'l прибавить CSLTH к CSADDR {начальный адрес следующей управляющей секции! end {while не конец! end {Pass 1} Рис. 3.10а. Алгоритм первого просмотра связывающего загрузчика. адрес загрузчик получает от операционной системы. (В гл. 6 мы обсудим, как операционная система может определить PROGADDR.) Переменная CSADDR содержит начальный адрес той управляющей секции, которая обрабатывается загрузчиком в данный момент. Этот адрес добавляется ко всем относитель- ным адресам данной управляющей секции для того, чтобы пре- образовать их в фактические адреса. Сам алгоритм показан на рис. 3.10а и 3.106. В процессе обсуждения этого алгоритма вам, возможно, будет полезно вспомнить примеры загрузки и связывания, рассмотренные в предыдущем разделе (рис. 3.8 и 3.9). Во время первого просмотра (рис. 3.10а) загрузчик обраба- тывает только запись-заголовок и записи-определения управля- ющих секций. Начальный адрес загрузки связываемой програм- мы (PROGADDR) загрузчик получает от операционной системы. Этот адрес становится начальным адресом (CSADDR) первой управляющей секции входного потока. Имя управляющей сек- ции, полученное из записи-заголовка, записывается в ESTAB и
3.2. Машиннно-зависимые свойства загрузчиков 141 Pass 2: begin занести в CSADDR значение PROGADDR занести в EXECADDR значение PROGADDR while не конец входного потока do begin прочитать следующую входную запись (запись-заголовок) занести в CSLTH длину управляющей секции while тип записи О 'Е' do begin прочитать следующую входную запись if тип записи = ZTZ then begin { если объектный код представлен в символьном виде, то преобразовать его во внутреннее представление) занести объектный код записи по адресу (CSADDR + указанный адрес) end (if 'Tz) else if тип записи « ZMZ then begin поиск модифицируемого имени в ESTAB if нашли then прибавить или вычесть значение имени к адрйСУ' (CSADDR + указанный адрес) else занести признак ошибки (неопределенное внешнее имя) end (if 'И') end (while О ZEZ) if адрес определен € в записе-конец 3 then занести в EXECADDR значение (CSADDR + указанный адрес) прибавить CSLTH к CSADDR end (while не конец) перейти по адресу, заданному в EXECADDR (начать исполнение программы! end (Pass 2) ‘ Рис. 3.1 Об. Алгоритм второго просмотра связывающего загрузчика. ему присваивается текущее значение CSADDR. Все внешни^ имена из записей-определений также заносятся в ESTAB. Зна^ чения их адресов получаются путем сложения значения из£ записи-определения с CSADDR. После того, как будет прочи- тана запись-конец, к CSADDR добавляется длина управляющей секции (CSLTH). (Длина была получена из записи-заголовка.) Таким образом, мы получаем начальный адрес для следующей управляющей секции. После завершения первого просмотра ESTAB содержит все внешние имена, определенные в данном наборе управляющих секций, вместе с назначенными им адресами. Многие загрузчи- ки могут по желанию пользователя выдавать на печать табли- цу загрузки, в которой показаны внешние имена и их адреса. Эта информация часто используется для отладки программ. Для примеров, приведенных на рис. 3.8 и 3.9, такая таблица загрузки может выглядеть следующим образом:
142 Гл. 3. Загрузчики и программы связывания Управляющая секция Имя Адрес Длина PROGA 4000 0063 LISTA 4040 ENDA 4054 PROGB 4063 007F LISTB 40C3 ENDB 40D3 PROGC 40E2 0051 LISTC 4112 ENDC 4124 По существу это та же информация, которая содержится в ESTAB в конце первого просмотра. Собственно загрузка, перемещение и связывание программы осуществляются во время второго просмотра (рис. 3.1 Об). Пе- ременная CSADDR используется так же, как и во время пер- вого просмотра. Она всегда содержит фактический начальный адрес загружаемой в данный момент управляющей секции. По- сле того, как считана очередная запись тела программы, содер- жащийся в ней объектный код помещается по указанному ад- ресу (плюс текущее значение CSADDR). Когда встречается запись-модификатор, то имя, требуемое для модификации, ищет- ся в ESTAB и затем его значение прибавляется к заданному адресу или вычитается из него. Завершает свою работу загрузчик обычно передачей управ- ления на загруженную программу. (В некоторых системах на- чальный адрес программы просто возвращается операционной системе и пользователь должен затем выполнить отдельную команду для начала выполнения программы.) Запись-конец каждой управляющей секции может содержать адрес первой команды данной секции, с которой должно начинаться ее ис- полнение. Наш загрузчик рассматривает этот адрес как адрес передачи управления для исполнения программы. Если адрес передачи управления задан более чем в одной управляющей секции, то загрузчик использует последний встретившийся. Если ни одна из управляющих секций не содержит адрес пе- редачи управления, то загрузчик использует в качестве адреса передачи управления начальный адрес программы (т. е. PROGADDR). Это соглашение типично для большинства связы- вающих загрузчиков. Обычно адрес передачи управления дол- жен помещаться только в записи-конце главной программы, но не в подпрограммах. В этом случае стартовый адрес будет пра- вильно определяться вне зависимости от порядка следования управляющих секций. (См. пример на рис. 2.17.) Вам следует применить данный алгоритм (вручную) для загрузки и связывания объектной программы, изображенной на
3.2. Машиннно-зависимые свойства загрузчиков 143 рис. 3.8. Если для PRO- GADDR использовать 4000, то полученный результат должен быть таким, как показано на рис. 3.9. Можно несколько повы- сить эффективность данного алгоритма, если слегка из- менить формат объектной программы. Суть этих из- менений заключается в том, что каждой внешней ссылке присваивается специальный ссылочный номер. Эти ссы- лочные номера использу- ются в записях-модификато- рах вместо имен. Предположим, что имя управляющей секции всег- да имеет ссылочный номер 01. Ссылочные номера ос- тальных внешних ссылок определены в записи-ссылке объектной программ дан- ной управляющей секции. На рис. 3.11 показано, как будет выглядеть объектная программа, изображенная на рис. 3.8, после внесения в нее указанных изменений. Для удобства чтения объ- ектной программы ссылоч- ные номера подчеркнуты. Описанный здесь механизм часто используется в реаль- ных загрузчиках, и это бы- jtg одна из причин, почему мы включили в наше объ- ектное представление за- писи-ссылки. Вы могли Рис. 3.11. Объектная програм- ма, соответствующая рис. 3.7, в которой используются ссылочные номера для моди- фикации кодов. (Для удоб- ства чтения ссылочные номера подчеркнуты.) HJPROGA 000000Л000063 nt1STA flOOOAOENDA ПОО054 ^02 LI STB 03ENDB £5ENDC Тл000020|ОА/р3201Вл77Юйй04лй5о014 TOOO054OF0O00i4FFFfF6OQQQ3F.00Q0I4.FEff CO >§)0902<0^02 Л Л Л Л *£0005406+04. *00005^06+05 *.000057,06,-04 *.0000 5А.06л+05 М00005А06л-04 ИОО 0 05 АР <4-01 H00005D.06-03 $0005$(£+OX *000060.06,4-02. НО 000 б 0,0 6/-01 £л000020__________ Й/ROGB £00000л00007£ tyLlSTB a000060aENDB £00070 J£2LI3TA £3ENDA. *£4LISTC !ГОО0036лОВаО 3100000£ 72027л05100000 trpO0O7OpFOOO00OFFFn6.FFfFfFFFFFFO00QO6Q4 *,00003 7,05^02 Ир0003ВД5А4-03. МРОООЗЕЛ05Л-Р2 ЭДРОО? 0,064-03 Ял000070л0бг02 нооооуор&оТ >ф00073£6А4ОЗГ М000074р4*04 КОООО76ОБ4-ОТ Цр 0 О О76.Ьб -04 Н00007бл0<+02 И000079.06403 HQ 0 0 07 9x06-0 21 Нр 0 0 07 С.0 6.+ 01 Hooo07c/)6A-oz HP.R0GC 000000,000051 D,LlSTe a000030aENDC .000042 :r£2LISTA £ЗЕШ 05KNDB Т,000018л0сл031OOOOOJ710000^05100000 Тд0ООО42,ОГ,0ОО030г000008лО00011£ОООоОлОООО0ф НО 0 001 <05,4-02 KpOOOj0,0.5,4-04 Н0000 21л05.+ 03 НО о 002^.05^-0 Z Нр00042£6л+0^ И000042л06г-0 2 *00004^06.4-01 НО 0004 ftp <40 2 H00D04 Вл06?+03 Нб0004В,Об‘-02 *£000 4 <р 6,-05 *р0004В.р<+04 *0 00 04 Ело 6+О 4 иЬ0004£,О6-О2 В
144 Гл. 3. Загрузчики и программы связывания заметить, что в алгоритме на рис. 3.10 эти записи не использо- вались. Основное преимущество использования ссылочных номеров заключается в том, что в этом случае отпадает надобность в многократном просмотре ESTAB для одних и тех же имен. Все внешние ссылки, которые используются в данной управляющей секции, могут быть найдены в ESTAB один раз. Затем, когда это потребуется, значение внешней ссылки можно получить по ее индексу в массиве ссылок. Вам предлагается разработать алгоритм, в котором используется такой механизм, и подумать, какие для этого потребуются дополнительные структуры данных. 3.3. Машинно-независимые свойства загрузчиков В этом разделе мы обсудим некоторые свойства загрузчи- ков, которые непосредственно не связаны со структурой машины. Загрузка и связывание часто рассматриваются как сервисные функции операционной системы. Взаимодействие програм- миста с загрузчиком менее тесное, чем, скажем, его взаимодей- ствие с ассемблером во время создания программы. Поэтому большинство загрузчиков имеет лишь незначительные особен- ности (и меньше различается по своим возможностям, чем ас- семблеры). В разд. 3.3.1 мы обсудим использование автоматического по- иска программ в библиотеках для разрешения внешних ссылок. Это средство позволяет программисту использовать стандарт- ные подпрограммы, не включая их явным образом в загружае- мую программу. Подключение библиотечных подпрограмм осу- ществляется автоматически во время связывания. В разд. 3.3.2 рассматриваются некоторые наиболее обще- употребительные дополнительные средства, которые програм- мист может использовать по своему усмотрению во время загрузки и связывания программ. Эти средства включают возможность задания альтернативного источника входной информации, изменение или уничтожение внешних ссылок, а также управление автоматической обработкой внешних ссылок. В разд. 3.3.3 мы более детально изучим концепцию оверлей- ной загрузки программ, с которой мы познакомились в разд. 2.4.1. Мы обсудим способы задания оверлейной структуры и рассмотрим, как она может быть реализована в процессе выполнения программы.
3.3. Машинно-независимые свойства загрузчиков 145 3.3.1. Автоматический поиск в библиотеках Многие связывающие загрузчики допускают автоматическое включение библиотечных подпрограмм в загружаемую програм- му. В большинстве случаев для этой цели используется некото- рая стандартная библиотека. Другие библиотеки могут подклю- чаться с помощью специальных управляющих директив или пе- редаваться загрузчику в качестве параметров. Этот механизм позволяет программисту использовать подпрограммы из одной или нескольких библиотек (например, математические или ста- тистические подпрограммы) почти так же, как если бы они были составной частью языка программирования. Подпрограм- ма, вызываемая из загружаемой программы пользователя, ав- томатически выбирается из библиотеки, связывается с главной программой и загружается в оперативную память. От програм- миста не требуется ничего, кроме как перечислить имена ис- пользуемых подпрограмм в исходной программе в списке внеш- них имен. В некоторых системах данный механизм называется автоматическим библиотечным вызовом {automatic library call). Мы используем термин библиотечный поиск {library search) для того, чтобы не путать его со средствами вызова, которые имеются в большинстве языков программирования. Связывающие загрузчики, поддерживающие библиотечный поиск, должны иметь список всех внешних имен, на которые есть ссылки, но которые не были определены во входном пото- ке загрузчика. Проще всего это можно сделать, занося каждое имя, содержащееся в записях-определениях, в таблицу внеш- них имен (ESTAB) еще до того, как оно будет фактически определено. Такие элементы таблицы помечаются специальным маркером, показывающим, что данное внешнее имя еще не определено. Когда встретится определение имени, то соответ- ствующий ему адрес заносится в таблицу и имя становится полностью определенным. В конце первого просмотра все не- определенные имена, оставшиеся в ESTAB, представляют собой неразрешенные внешние ссылки. Для их разрешения загрузчик должен просмотреть все заданные библиотеки и обработать найденные в них подпрограммы точно так же, как если бы они являлись частью входного потока. Заметим, что библиотечные подпрограммы, в свою очередь, могут иметь внешние ссылки. Поэтому поиск в библиотеках надо проводить до тех пор, пока не будут разрешены все внешние ссылки (или пока дальнейшее разрешение ссылок ста- нет невозможным). Если после завершения поиска в библиоте- ках останутся неразрешенные внешние ссылки, то их следует рассматривать как ошибки. Описанный процесс позволяет программисту замещать стан- дартные библиотечные подпрограммы своими собственными
146 Гл. 3. Загрузчики и программы связывания подпрограммами. Предположим, например, что главная про- грамма ссылается на стандартную подпрограмму SQRT. Обыч- но подпрограмма с этим именем выбирается из библиотеки и подключается автоматически. Если программист по каким-то соображениям захочет использовать другую версию подпро- граммы SQRT, то он может просто включить ее во входной поток. После завершения первого просмотра загрузчика ссылка на SQRT будет определена, и поэтому ее не потребуется искать в библиотеках. Загрузчик обычно работает с библиотеками, которые содер- жат ассемблированные или откомпилированные версии подпро- грамм (т. е. объектные программы). Просмотр библиотек мож- но осуществлять с помощью сканирования записей-определений всех объектных программ библиотеки, но это крайне неэффек- тивно. Обычно для библиотек используется специальная файло- вая структура. Она содержит каталог (директорий), в кото- ром перечислены имена всех подпрограмм и имеются указатели на их месторасположения в файле. Если подпрограмма до- пускает вызов по нескольким именам (используя различные входные точки), то все ее альтернативные имена также присут- ствуют в каталоге. Конечно, сама объектная программа хра- нится в единственном экземпляре, а все альтернативные имена указывают на одну и ту же копию программы. Таким образом, фактически библиотечный поиск сводится к просмотру каталога и последующему чтению найденной объектной программы. Не- которые операционные системы могут постоянно хранить ката- логи наиболее часто используемых библиотек в оперативной памяти. Это позволяет ускорить процесс поиска, если требуется обработать большое количество внешних ссылок. Мы рассмотрели библиотечный поиск применительно к раз- решению ссылок на подпрограммы. Очевидно, что те же тех- нические приемы могут быть одинаково хорошо применимы для разрешения любых других типов внешних ссылок. 3.3.2» Управление процессом загрузки Многие загрузчики допускают задание управляющих пара- метров, которые позволяют изменять стандартный процесс, описанный в разд. 3.2. В этом разделе мы остановимся на не- которых типичных вариантах управления работой загрузчика и рассмотрим примеры их использования. Для задания управляю- щих параметров многие загрузчики используют специальный командный язык. Иногда для задания предложений командно- го языка используется отдельный входной файл. В других слу- чаях эти предложения могут вставляться в основной входной поток между объектными программами. Отдельные системы даже позволяют включать команды управления загрузчиком в
3.3 Машинно-независимые свойства загрузчиков 147 исходную программу, а ассемблер или компилятор переносит их в объектную программу. В этом разделе мы будем считать, что параметры, управля- ющие работой загрузчика, задаются с помощью командного языка, хотя существуют и другие способы их задания. В неко- торых системах команды управления загрузчиком являются со- ставной частью языка управления заданиями, и поэтому они обрабатываются операционной системой. В этом случае опе- рационная система собирает все параметры в управляющий блок, который становится доступным загрузчику после начала его работы. Собственно реализация тех или иных возможностей, предоставляемых загрузчиком, конечно, остается той же самой, за исключением самих средств задания управляющих парамет- ров. Одной из типичных возможностей, предоставляемых пользо- вателю, является определение альтернативного источника вход- ного потока. Например, команда INCLUDE имя-программы(имя-библиотеки) может предписывать загрузчику прочитать определенную про- грамму из библиотеки и работать с ней так, как если бы она входила в состав исходного входного потока загрузчика. Другие команды дают пользователю возможность исключить некоторые внешние имена или целые управляющие секции. Кро- ме того, может разрешаться изменять внешние ссылки во вре- мя загрузки и связывания программ. Например, команда DELETE имя-секции может предписывать загрузчику исключить из рассмотрения указанную управляющую секцию входного потока, а команда CHANGE имя1,имя2 может использоваться для замены внешнего имени имя! на имя2. Ниже приводится пример использования этих команд. Рассмотрим исходную программу, изображенную на рис. 2.15, и соответствующую ей объектную программу на рис. 2.17. Имеется главная программа (СОРУ), которая использует две подпрограммы (RDREC и WRREC). Каждая из программ оформ- лена в виде отдельной управляющей секции. Если RDREC и WRREC разработаны только для использования в программе СОРУ, то весьма вероятно, что все три управляющие секции будут ассемблироваться одновременно. Это означает, что все три управляющие секции будут расположены в одном файле к(или будут включены в одну и ту же библиотеку). Предположим теперь, что в вычислительной системе появил- ся набор сервисных подпрограмм. Две из них, READ и WRITE, предназначены для выполнения тех же самых функций, что и
118 Гл. 3. Загрузчики и программы связывания 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, которые расположены в библиотеке математической статистики, она может проводить анализ данных. Пользователь может потребовать проведение такого анализа в процессе
3.3. Машинно-независимые свойства загрузчиков 149 выполнения программы. Поскольку программа содержит ссылки на эти подпрограммы, то в общем случае они были бы за- гружены в оперативную память и связаны с главной програм- мой. Если известно, что в данном конкретном запуске програм- мы выполнение статистического анализа не потребуется, то пользователь может включить в свое задание команду типа NOCALL STDDEV,PLOT,CORREL которая предписывает загрузчику оставить соответствующие внешние ссылки неразрешенными. Это позволяет избежать на- кладных расходов на загрузку и связывание ненужных подпро- грамм и экономит место в оперативной памяти. Кроме того, пользователь может вообще отказаться от ав- томатического библиотечного поиска. Естественно, что если во время исполнения программа попытается воспользоваться не- разрешенными ссылками, то это приведет к ошибке. Данный режим бывает полезен тогда, когда требуется осуществить только связывание программы без ее немедленного выполнения. В этом случае часто бывает желательно отложить разрешение внешних ссылок на более позднее время. В разд. 3.4.1 мы рас- смотрим редактор внешних связей, который выполняет эту функцию. Другим общеупотребительным средством является управле- ние информацией, выводимой загрузчиком на печать. В разд. 3.2.3 мы привели пример таблицы загрузки, которая может создаваться в процессе работы загрузчика. С помощью команд пользователь может указать, нужна ему распечатка данной таблицы или нет. Если таблица нужна, то можно ука- зать уровень ее детализации. Например, таблица может вклю- чать только имена управляющих секций и назначенные им ад- реса. Кроме того, она может включать адреса всех внешних имен и даже таблицу перекрестных ссылок, в которой показа- ны все ссылки на каждое внешнее имя. Очень часто загрузчики предоставляют и другие дополни- тельные возможности. Одна из них — определение стартовой точки программы (она заменяет любую информацию о старто- вой точке, заданную в объектной программе). Другая — разре- шение или запрещение выполнения программы, если в процес- се ее загрузки были обнаружены ошибки (например, неразре- шенные внешние ссылки). 3.3.3. Оверлейная структура программ В разд. 2.4.1 мы обсуждали реализацию двухпросмотрового ассемблера с простой оверлейной структурой. Такой способ ре- ализации позволяет сократить суммарный объем оперативной памяти, требуемой для ассемблирования, так как для первого
150 Гл. 3. Загрузчики и программы связывания и второго просмотров используется одно и то же пространство. В этом разделе мы рассмотрим более сложный пример и об- судим, как осуществляется управление самим оверлейным про- цессом. В качестве введения в данную тему вам следует об- • ратиться к рис. 2.18 и 2.19 и материалу разд. 2.4.1. На рис. 3.12а показана программа с оверлейной структурой, которую мы будем использовать в Управляющая секция А В С В Е F й Н 1 Длина (в байтах) 1000 1800 400G 2800 800 1000 40Q 800 1000- качестве примера. Буквами обозначены имена управ- ляющих секций, а линия- ми — связи между ними по передаче управления. Таким образом, корневая управляющая секция (со- ответствующая ведущей подпрограмме на рис. 2.18) имеет имя А. Уп- равляющая секция А мо- жет вызывать секции В, С или D/Е; секция В мо- жет вызывать F/G или Н и т. д. Обозначение D/Е используется для спецификации двух уп- равляющих секций (D и Е), которые тесно связа- ны между собой и всегда используются совместно. Хотя эти управляющие секции раздельно обра- батываются ассемблером 4 800 К 2000 6 Рис. 3.12. Пример оверлейной программы. и загрузчиком, однако в оверлейной структуре они рассматриваются как одно целое. Например, t) может содержать испол- няемые команды, в то время как Е — связанные с ними данные. Другой пример — Е может содержать часто используемые в D подпрограммы. На рис. 3.12в показана длина (в шестнадцате- ричном виде) каждой управляющей секции. Большинство систем, поддерживающих оверлейные програм- мы, требует, чтобы они имели древовидную структуру (т. е. та- кую, как на рис. 3.12а). Узлы такого дерева называются сег- ментами. Корневой сегмент загружается в самом начале испол- нения программы и остается в памяти до окончания счета. Остальные сегменты загружаются тогда, когда к ним происхо- дит обращение. Предположим, например, что управляющая сек- ция А вызывает В. Этот вызов приведет к автоматической
3.3. Машинно-независимые свойства загрузчиков 151 загрузке сегмента, содержащего В (если только он уже не нахо- дится в оперативной памяти). Если позднее А вызовет D, то будет загружен сегмент, содержащий секцию D. Порядок рас- положения сегментов, находящихся на одном уровне оверлей- ной структуры, значения не имеет. Например, А может сначала вызвать С, затем В, затем D/Е, потом вновь С и т. д. Предположим, что в некоторый момент времени исполняется управляющая секция Н. Она была вызвана секцией В, которую в свою очередь вызвала секция А. Таким образом, эти три сегмента являются активными и должны находиться в опера- тивной памяти. Другие сегменты не могут быть активными, поскольку нет путей, связывающих их с Н. Например, если ра- нее был вызван сегмент, содержащий К, то он должен был вернуть управление D/Е (и затем А) прежде, чем В мог быть вызван из секции А. Данные рассуждения приводят нас к пра- вилу, которое используется в большинстве систем, допускаю* щих оверлейные структуры: если сегмент S находится в опера- тивной памяти, то все другие сегменты, расположенные на пути, ведущем из S к корню, также должны находиться в опе- ративной памяти. Соблюдение этого правила упрощает органи- зацию оверлея. Если некоторый сегмент вызывает сегмент, рас- положенный на более низком уровне древовидной структуры (т. е. находящийся дальше от корня), то может потребоваться загрузка этого сегмента в оперативную память. С другой сто- роны, если некоторый сегмент ссылается на сегмент, лежащий между ним и корнем, то этот сегмент уже находится в памяти. Поскольку сегменты одного уровня (например, В, С и D/E) могут вызываться только из сегмента вышестоящего уровня, то они не требуются, одновременно. Поэтому они могут совместно использовать одну и ту же область оперативной памяти. Если передача управления вызывает загрузку некоторого сегмента, то он замещает в памяти сегмент, который находится на одном с ним уровне (и сегменты ему подчиненные). Таким образом, общий объем оперативной памяти, требуемой для исполнения программы, оказывается меньше. Это и является основной при- чиной использования оверлейных структур. Оверлейная структура программы описывается при помощи команд загрузчика, похожих на те, что мы рассматривали в предыдущем разделе. На рис. 3.13 приведен набор команд, ко- торый задает оверлейную структуру, изображенную на рис. 3.12а. Команда SEGMENT имя-сегмента (управляющая-секция...) определяет сегмент (т. е. узел древовидной структуры). Данная команда задает имя сегмента и описывает список входящих в него управляющих секций. Первый определяемый сегмент — это корень. Два последовательных предложения SEGMENT
152 Гл. 3. Загрузчики и программы связывания задают между описываемыми сегментами отношение вида отец —• сын. Поэтому на рис. 3.13 первые три предложения описывают самый левый путь (от корня к F/G) структуры на рис. 3.12а. Остальные сегменты, имеющие общего отца, на рис. 3.13 задаются с помощью предложений другого типа. Предложение PARENT имя-сегмента описывает сегмент (ранее определенный), который является от- цом следующего за ним сегмента. Таким образом, на рис. 3.13 первое предложение PARENT определя- ет, что SEG2 является отцом SEG4. Если бы этого предложения не было, то от- цом SEG4 считался бы сегмент SEG3. Вам надлежит тщательно изучить остальные определения и убедиться, что они действительно описывают дре- вовидную структуру, изображенную на рис. 3.12а. После того, как мы задали оверлей- ную структуру, определение начальных адресов сегментов становится простым делом, так как каждый сегмент начина- ется сразу после своего отца. Правило, управляющее размещением сегментов в оперативной памяти, обеспечивает раз- мещение всех загруженных на данный момент сегментов в областях, смежных с областью, выделенной заданию в нача- ле его работы. На рис. 3.14а показаны длина и относительный адрес начала каждого сегмента рассматриваемой на- этом же рисунке показаны фактические начальные адреса (считается, что начальный адрес загрузки равен 8000). Во время выполнения программы в памяти могут присутствовать различные наборы сегментов. На рис. 3.146 по- казаны три возможных варианта. С остальными вам предлага- ется разобраться самостоятельно. Позднее мы увидим, что за- грузчик может добавить одну или несколько специальных уп- равляющих секций, содержащих информацию для управления оверлейной структурой. Естественно, что длина этих секций должна учитываться при назначении адресов. Как мы видели, загрузчик может назначить фактический адрес загрузки каждому сегменту оверлейной структуры после того, как будет определен начальный адрес. Таким образом, становятся известны адреса всех внешних имен. Это означает, что все операции перемещения и связывания могут выполнять- ся обычным образом, но с одним исключением — отец должен SEGMENT SEG1(A) SEGMENT SEG2(B). SEGMENT SEG3(F,G) PARENT SEG2 SEGMENT SEG4(H) PARENT SEG1 SEGMENT SEG5(C) PARENT SEG1 SEGMENT SEG6(D,E) SEGMENT SEG7(I) PARENT SEG6 SEGMENT SEG8(J) PARENT SEG6 SEGMENT SEG9(K) Рис. 3.13. Определения оверлейной структуры с помощью управляющих предложений. ми программы. На
3.3. Машинно-независимые свойства загрузчиков 153 иметь возможность передать управление сегменту, который в этот момент не находится в оперативной памяти. Загрузка кор- невого сегмента может производиться непосредственно в опера- тивную память. Остальные сегменты (для которых уже выпол- нены операции перемещения и связывания) записываются в Начальный адрес ~ „ Факт и - Сегмент Относительным ческий Длина специальный файл (SEG- FILE), создаваемый за- грузчиком. Собственно оверлей- ный процесс (т. е. загруз- ка сегмента, когда ему передается управление) может осуществляться различными способами. Здесь мы опишем простой механизм, который не требует вмешательства операционной системы. Наряду с ним могут ис- 1 0000 ВООО 1000 2 1000 9000 1800 3 2800 А800 1400 4 2800 А800 800 5 1000 9000 4000 6 1000 9000 3000 7 4000 сооо 1000 8 4000 сооо 800 9 4000 сооо 2000 средства, аппарату связыва- пользоваться аналогичные динамического ния (см. разд. 3.4.2). Фактическая загрузка сегментов в процессе ис- полнения программы осу- ществляется специальной программой — менедже- ром оверлея (overlay ma- nager). В нашем приме- ре менеджер оверлея | расположен в отдельной управляющей секции с 8000 9000 А000 вооо C0OO Dooo а именем OVLMGR. Эта Рис. 3.14. Распределение памяти для овер- секция автоматически лейной программы. включается загрузчиком в корневой сегмент оверлейной программы. Для управления за- грузкой сегментов OVLMGR должен иметь информацию об оверлейной структуре программы. Эта информация хранится в таблице сегментов (SEGTAB), которая создается загрузчиком и выключается в корневой сегмент в виде отдельной управляю- щей секции. Таблица SEGTAB описывает древовидную струк- туру, определяя уровень расположения каждого из сегментов. Для каждого сегмента она также содержит его начальный ад- рес загрузки, адрес входной точки и месторасположение сег- мента в SEGFILE. (В данной реализации мы полагаем, что сегмент может вызываться только по одной входной точке.)
154 Гл. 3. Загрузчики и программы связывания Кроме того, для каждого сегмента, за исключением корня в SEGTAB, имеется область передачи управления. Она содер- жит команды, которые обеспечивают передачу управления на требуемый сегмент. Если сегмент уже загружен в оперативную память, то его область передачи управления содержит простую команду перехода на входную точку сегмента. Если сегмент не загружен, то область передачи управления содержит команды активизации OVLMGR и передачи ему информации, необходи- мой для загрузки сегмента. В процессе своей работы мене- джер оверлея изменяет команды в области передачи управле- ния так, чтобы они всегда соответствовали текущему статусу каждого из сегментов. Обращение одного сегмента к другому, стоящему на следующем уровне древовидной структуры (т. е. обращение, которое может потребовать оверлейную загрузку), преобразуется загрузчиком в обращение к области передачи управления соответствующего сегмента. Область передачи управления всегда присутствует в оперативной памяти, так как SEGTAB расположена в корневом сегменте. Затем команды области передачи управления либо непосредственно передают управление на требуемый сегмент, либо активизируют OVLMGR. В последнем случае OVLMGR загружает требуемый сегмент, обновляет SEGTAB и после этого передает управление сегменту. Иллюстрация описанного процесса дана на рис. 3.15. На рис. 3.156 показано содержимое оперативной памяти на неко- тором этапе исполнения программы. В данный момент загру- жены Сегменты 1, 2, 4 и исполняются команды управляющей секции А. Сегменты 2 и 4 были загружены в ответ на ранее выполненные команды вызова. Хотя сейчас управление верну- лось Сегменту 1, однако Сегменты 2 и 4 продолжают оставать- ся в оперативной памяти до тех пор, пока их не заменят дру- гие сегменты. Области передачи управления Сегментов 2 и 4 содержат команды перехода на управляющие секции В и Н. Остальные области передачи управления содержат команды активизации OVLMGR (соответствующие им поля SEGTAB за- штрихованы). Если в управляющей секции А сейчас будет вы- полнена команда вызова секции В (в исходной программе команда -j-JSUB В), то в результате выполнится переход в область передачи управления Сегмента 2. Как показано на рисунке, это приведет к прямой передачи управления на сек- цию В. Предположим теперь, что А вызывает D. На рис. 3.15 пока- зано, что область передачи управления, соответствующая Сег- менту 6, содержит команды активизации OVLMGR. Менеджер оверлея загрузит Сегмент 6 из SEGFILE на выделенное ему место в оперативной памяти. Затем он обновит SEGTAB, ука- зав, что в данный момент Сегмент 6 находится в памяти, а
3.3. Машинно-независимые свойства загрузчиков 155 OVLMGR SEGTAB I Корневой сегм^НТ " (S£GJ) Сегмент * (SEG6) Сегменты 2 и 4 нет. Заметим, что Сегмент 4 должен быть удален из памяти (даже если занятое им про- странство не требуется для ново- го сегмента), так как удален его отец. Теперь для вызова секции D остается передать управление на входную точку- Сегмента 6 (см. рис. 3.15в). Возврат управления из вызван- ного сегмента (например, из D в А) может выполняться обычным образом. (Активизация OVLMGR для этого не требуется.) Например, команда JSUB (рис. 3.156) может занести адрес возврата в регистр L. Менеджер оверлея сохранит это значение. Таким образом, по за- вершению своей работы управляю- щая секция D может просто вы- полнить команду RSUB. Это тот Рис. 3.15. пример управления же самый механизм, который ис- оверлеем. пользуется для программ без овер- лейной структуры. В некоторых системах функции OVLMGR выполняются спе- циальным блоком операционной системы, который называется
156 Гл. 3. Загрузчики и программы связывания супервизором оверлея [overlay supervisor). В этом случае SEGTAB содержит макрокоманды операционной системы, обес- печивающие загрузку требуемого сегмента. 3.4. Варианты построения загрузчиков В данном разделе мы рассмотрим некоторые общие вариан- ты реализации функций загрузчика, включая перемещение и связывание. Связывающие загрузчики, рассмотренные в разд. 3.2.3, выполняют связывание и перемещение в процессе загрузки. Здесь мы обсудим две альтернативные схемы — ре- дакторы связей, выполняющие связывание перед загрузкой, и динамическое связывание, в котором функции связывания осу- ществляются во время исполнения программы. В разд. 3.4.1 рассматривается редактор связей, который во многих вычислительных системах используется вместо связы- вающего загрузчика или в качестве его дополнения. Редактор связей осуществляет связывание и некоторые действия по пере- мещению, однако подготовленная им программа загружается не в оперативную память, а записывается в файл или в библи- отеку. Этот подход позволяет сократить накладные расходы на обработку программы, так как загрузчику остается выполнить лишь очень простые операции настройки относительных ад- ресов. В разд. 3.4.2 мы познакомимся с механизмом динамического связывания. В этой схеме загрузка программ осуществляется в момент первого вызова, и для этого используются средства операционной системы. Откладывая связывание до момента вы- зова, мы можем получить дополнительную гибкость в организа- ции программ. Однако обычно это ведет к дополнительным по сравнению со связывающим загрузчиком накладным расходам. В разд. 3.4.3 мы обсудим раскручивающие загрузчики (boob strap loaders). Такие загрузчики могут использоваться для про- грамм, выполняемых без операционной системы или системного загрузчика. Они также могут применяться для загрузки в опе- ративную память самой операционной системы или системного загрузчика. 3.4.1. Редакторы связей Основное различие между редактором связей и связываю- щим загрузчиком показано на рис. 3.16. Вначале исходный текст переводится ассемблером или компилятором в объектную программу (которая может состоять из нескольких управляю- щих секций). Связывающий загрузчик выполняет все операции
3.4. Варианты построения загрузчиков 157 связывания и перемещения, включая, если это необходимо, и автоматический библиотечный поиск, и загружает подготовлен- ную для исполнения программу непосредственно в оперативную память. С другой стороны, редактор связей подготавливает ва- риант программы с разрешенными внешними связями (его ча- сто называют загрузочным модулем или исполняемой програм- мой) и записывает его в файл или в библиотеку. Рис. 3.16. Обработка объектной программы с использованием связывающего загрузчика (а) и редактора связей (б). Для загрузки программы, подготовленной редактором свя- зей, может быть использован простой перемещающий загруз- чик. Ему остается только прибавить адрес начала загрузки ко всем относительным адресам внутри программы. Редактор свя- зей выполняет размещение всех управляющих секций относи- тельно начала программы. Таким образом, все поля, требующие модификации во время загрузки, получают значения относи- тельно начала программы. Это означает, что загрузка может выполняться за один просмотр без использования таблицы внешних ссылок. По сравнению со связывающим загрузчиком такая загрузка требует намного меньших накладных расходов. Если программа должна выполняться много раз без переас- семблирования, то использование редактора связей позволяет существенно снизить затраты на разрешение внешних связей и
158 Гл. 3. Загрузчики и программы связывания библиотечный поиск, так как они в этом случае выполняются только один раз, в то время как связывающий загрузчик вы- полняет их при каждом запуске программы. Однако иногда программа переассемблируется практически при каждом новом запуске. Такая ситуация характерна при разработке и тестировании программ (например, обработка студенческих заданий). Кроме того, если программа использует- ся довольно редко, то может оказаться нецелесообразным хра- нить ее ассемблированную версию в библиотеке. В этих слу- чаях связывающий загрузчик является более эффективным, так как он позволяет обойтись без операций записи и чтения за- грузочного модуля. Программа, создаваемая редактором связей, обычно записы- вается в форме, удобной для ее обработки перемещающим за- грузчиком. В ней разрешены все внешние связи, а информация о перемещаемых адресах задается с помощью записей-модифи- каторов или масок перемещения. Несмотря на то что все опе- рации связывания уже выполнены, в загрузочном модуле часто сохраняется информация о внешних ссылках. Это позволяет впоследствии проводить в загрузочном модуле операции по за- мене управляющих секций, изменению внешних связей и др. Если такую информацию не сохранять, то последующая обра- * ботка загрузочного модуля с помощью редактора связей будет невозможна. В этом случае единственное, что можно будет делать с программой,— это загружать ее в память и ис- полнять. Если фактический адрес загрузки программы известен зара- нее, то редактор связей может выполнить всю необходимую настройку. В результате будет получено представление про- граммы, которое в точности соответствует ее виду во время исполнения. Такая программа ничем не отличается от абсолют- ной объектной программы. Однако обычно возможность загру- жать программу в любое место памяти вполне компенсирует те расходы, которые требуются для выполнения настройки отно- сительных адресов во время загрузки программы. Кроме подготовки объектной программы редактор связей может выполнять и много других полезных функций. Рассмот- рим, например, программу (PLANNER), которая использует большое количество подпрограмм. Предположим, что в одну из этих подпрограмм (PROJECT) внесены изменения (например, для исправления ошибок или повышения эффективности). По- сле ассемблирования или компилирования новой версии PROJECT мы должны включить ее в PLANNER. Редактор свя- зей позволяет сделать это, не возвращаясь к исходным (раздель- ным) версиям всех программ. Ниже приведена последователь- ность команд редактора связей, которая позволяет выполнить эту работу. Используемый здесь язык аналогичен тому, что мы
3.4. Варианты построения загрузчиков 159 рассматривали в разд. 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, а не
160 Гл. 3. Загрузчики и программы связывания отдельные подпрограммы. Поскольку в FTNIO все внешние связи между подпрограммами уже разрешены, то, следователь- но, они не будут обрабатываться при обработке пользователь- ской программы. В результате повысится эффективность выпол- нения операции связывания и будут значительно снижены сум- марные затраты на работу системы. Редакторы связей часто разрешают пользователю указывать те внешние ссылки, которые не должны обрабатываться с по- мощью автоматического библиотечного поиска. Предположим, например, что в библиотеке должно храниться 100 Фортран- программ, в которых используются описанные выше подпро- граммы ввода-вывода. Если все эти программы хранить с пол- ностью разрешенными внешними связями, то в библиотеке бу- дет записано 100 копий FTNIO. Если библиотечное простран- ство дорого, то это может оказаться крайне нежелательным. В этом случае пользователь может с помощью команд, похожих на те, что были рассмотрены в разд. 3.3.2, запретить выполне- ние библиотечного поиска во время работы редактора связей. Тогда будут разрешены только внешние связи между пользо- вательскими подпрограммами. Впоследствии во время испол- нения программы для объединения пользовательской програм- мы с модулем FTNIO можно будет использовать связывающий загрузчик. Поскольку в этом случае потребуется выполнить две отдельные операции связывания, то это приведет к некоторому увеличению накладных расходов, но зато позволит сэкономить библиотечное пространство. Редакторы связей часто обеспечивают выполнение и других операций и команд, подобных тем, что мы рассматривали для связывающих загрузчиков. По сравнению со связывающими за- грузчиками редакторы связей в общем случае предоставляют большую гибкость и разнообразие средств управления загруз- кой. Однако за это приходится расплачиваться ростом сложно- сти программного обеспечения и увеличением накладных рас- ходов. 3.4.2. Динамическое связывание Редакторы связей выполняют операции связывания до того, как программа загружается для исполнения. Связывающий за- грузчик выполняет эти же операции во время загрузки про- граммы. Здесь мы рассмотрим схему загрузки, в которой свя- зывание откладывается до момента исполнения программы. В этом случае загрузка и связывание подпрограммы осущест- вляются тогда, когда к ней происходит первое обращение. Та- кая организация процесса загрузки обычно называется динами- ческим связыванием (dynamic linking) или динамической за- грузкой (dynamic loading).
3.4. Варианты построения загрузчиков 161 По сравнению с ранее обсуждавшимися схемами обработки внешних связей динамическое связывание имеет ряд преиму- ществ. Предположим, к примеру, что программа содержит под- программы, предназначенные для исправления или подроб- ного диагностирования ошибок во входных данных. Если такие ошибки редки, то в большинстве запусков программы эти под- программы будут не нужны. Однако если программа полно- стью связывается до начала исполнения, то эти подпрограммы должны загружаться (и для них должны обрабатываться внеш- ние связи) каждый раз, когда исполняется программа. Дина- мическое связывание позволяет загружать подпрограммы в том (и только в том) случае, когда они действительно нужны. Если соответствующие программы занимают много места или имеют много внешних связей, то это может дать существенную экономию времени и пространства оперативной памяти. Похожая ситуация возникает и тогда, когда при каждом конкретном запуске программы используется лишь незначи- тельная часть подпрограмм из большого числа потенциально используемых. Причем, какие именно подпрограммы будут ис- пользоваться, становится известно только после обработки входного потока. Такая организация программы характерна, например, для интерактивных систем, в которых пользователю разрешается в процессе диалога вызывать различные библио- течные подпрограммы. В этом случае входные данные вводят- ся пользователем с клавиатуры, а полученные результаты выводятся на экран терминала. В каждом сеансе работы поль- зователь может потенциально обратиться к любой библиотеч- ной подпрограмме, однако фактически используется лишь их небольшая часть. Благодаря динамическому связыванию отпа- дает необходимость загружать при каждом запуске все библио- течные подпрограммы. Более того, динамическое связывание позволяет главной программе вообще ничего не знать о том, с каким набором подпрограмм она должна работать, так как имена подпрограмм могут обрабатываться точно так же, как и другие входные данные. Существуют различные механизмы, позволяющие осущест- влять загрузку и связывание в момент вызова подпрограммы. Здесь мы обсудим подход, при котором динамическое связыва- ние осуществляется с помощью запросов к операционной систе- ме. При желании можно рассматривать эти запросы как обра- щение к резидентной части загрузчика. При динамическом связывании программа вместо того, что- бы выполнять команду JSUB, в которой используется внешнее- имя, должна выполнить запрос к операционной системе на за- грузку и исполнение подпрограммы. Параметром этого запроса является символическое имя требуемой подпрограммы Арис. 3.17а), Операционная система по своим внутренним^ 6 Зак^ 792
!С2 Гл. 3. Загрузчики и программы связывания Загрузка-И-вызов ERRHANDL кий загруз- чик (блок операционной системы) Программа пользователя Рис. 3.17. Загрузка и вызов подпрограмм с помощью динамического свя- зывания. таблицам определяет, загружена ли данная подпрограмма в данный момент времени или нет. Если это необходимо, то под- программа загружается из пользовательской или системной
3.4. Варианты построения загрузчиков 163 библиотеки (рис. 3.176). После этого операционная система вызывает требуемую подпрограмму (рис. 3.17в). После завершения своей работы подпрограмма возвращает управление в точку, откуда она была вызвана (т. е. сервисной подпрограмме операционной системы, выполняющей динамиче- скую загрузку). Затем операционная система возвращает уп- равление программе, выдавшей запрос. Этот процесс показан на рис. 3.17г. Очень важно, чтобы возврат из подпрограммы осуществлялся через операционную систему, так как в этом случае ей будет известен момент завершения вызванной под- программы. После окончания работы подпрограммы занятая ей оперативная память освобождается и может быть использована для других целей. Однако это необязательно делать немедлен- но. Иногда желательно сохранить подпрограмму в памяти для последующего использования до тех пор, пока не потребуется память для других процессов. Если подпрограмма все еще на- ходится в памяти, то ее повторный вызов может осуществляться без перезагрузки. В этом случае динамический загрузчик про- сто может передать на нее управление, как это показано на рис. 3.17д. При динамическом связывании установление соответствия между символическим именем вызываемой подпрограммы и фактическим адресом ее загрузки не делается до тех пор, пока не будет выполнена команда вызова. Другими словами можно сказать, что привязка имени к фактическому адресу отклады- вается до момента исполнения программы. Как мы уже гово- рили, это дает нам большую гибкость в организации програм- мы. В то же время это приводит к дополнительным накладным расходам, так как операционная система должна участвовать в процессе вызова подпрограмм. В последующих главах мы уви- дим другие примеры, в которых используется откладывание привязки. Как и в данном случае, такая отложенная привязка дает большие возможности за дополнительную плату. 3.4.3. Раскручивающие загрузчики В нашем обсуждении мы не затрагивали один важный во- прос: как сам загрузчик загружается в оперативную память? Конечно, можно сказать, что он загружается операционной си- стемой, но тогда возникает вопрос: кто загружает операцион- ную систему? В более общем виде вопрос можно сформулиро- вать так: каким образом можно запустить в работу пустую ма- шину, в оперативной памяти которой нет ни одной программы?. В ситуации, когда машина пуста, необходимости в переме< щении программ не возникает. Мы можем просто определить1 произвольный абсолютный адрес первоначальной загрузки про*! граммы. Наиболее часто этой программой будет олерадионна^ 6*
164 Гл. 3. Загрузчики и программы связывания система, которая занимает заранее определенное место в памя- ти. Это означает, что нам нужны некоторые средства для вы- полнения функций абсолютного загрузчика. Одно из возможных решений — поручить оператору ввести в оперативную память объектный код абсолютного загрузчика с помощью переключа- телей на пульте ЭВМ. Некоторые машины требуют именно это. Однако такая процедура слишком неудобна и не застрахована от ошибок, чтобы быть действительно хорошим решением дан- ной проблемы. Другое решение — иметь программу абсолютного загрузчика в качестве резидента, записанного в память, доступную только для чтения (постоянное запоминающее устройство — ПЗУ). Ко- гда возникает некоторый аппаратный сигнал (например, опера- тор нажимает кнопку «старт системы»), машина начинает вы- полнять программу, записанную в ПЗУ. В некоторых машинах программа исполняется непосредственно в ПЗУ, в других —она копируется в основную память и исполняется в ней. Однако не все машины имеют ПЗУ. Кроме того, использование ПЗУ за- трудняет модификацию абсолютного загрузчика, если в этом возникает необходимость. Промежуточное решение заключается в том, чтобы иметь встроенные аппаратные средства (или очень короткую програм- му в ПЗУ), которые позволяют прочитать запись фиксирован- ной длины с некоторого внешнего запоминающего устройства в фиксированное место оперативной памяти. Используемое кон- кретное устройство обычно можно задать с помощью пультовых переключателей. После того как запись будет прочитана в па- мять, управление передается на ее начало. Считанная запись содержит команды загрузки абсолютного загрузчика. Если команды, требуемые для организации процесса загрузки, не по- мещаются в одну запись, то первая запись может служить для чтения еще одной записи, а та в свою очередь может прочитать еще и другие записи. Первая запись (или записи) обычно на- зывается раскручивающим загрузчиком (bootstrap loader). Та- кие загрузчики должны находиться в начале любой объектной программы, которая предназначена для работы на пустой ма- шине. К числу таких программ относятся, например, сами опе- рационные системы и все другие программы, которые должны выполняться без операционной системы. 3.5. Примеры реализации В данном разделе мы кратко познакомимся с редакторами связей и загрузчиками для реальных машин. Поскольку транс- ляция и загрузка взаимосвязаны, мы в качестве примеров бу- дем использовать те же самые три машины, что и в разд. 2.5.
3.5. Примеры реализации 165 При чтении данного раздела вам следует обращаться к описа- ниям машин, которые были даны в гл. 1. Как и ранее, мы не ставим перед собой цели дать полное описание рассматриваемых редакторов связей и загрузчиков. Вместо этого особое внимание будет уделено отдельным инте- ресным или необычным возможностям и отличиям данных реа- лизаций от общей модели, рассмотренной нами в предыдущем разделе. Мы также укажем на те свойства редакторов связей и загрузчиков, которые обусловлены особенностями ассемблеров или структуры ЭВМ. 3.5.1. Редактор связей System/370 Формат объектной программы, обрабатываемой редактором связей System/370, очень похож на тот, что мы обсуждали в связи с УУМ/ДС. Для повышения эффективности используется механизм ссылочных номеров, рассмотренный нами в разд. 3.2.3. Выходная программа редактора связей называется загрузочным модулем (load module). Загрузочные модули могут загружать- ся в оперативную память и исполняться. Они также (обычно), содержат информацию, необходимую для их последующей об- работки с помощью редактора связей. Однако пользователь может указать, что некоторый модуль является «нередактируе- мым», и в этом случае большая часть управляющей информа- ции будет опущена. Это позволяет делать более компактные загрузочные модули. Редактор связей System/370 может выполнять все обсуждав- шиеся нами стандартные операции. Управляющие секции мож- но исключать, заменять или реорганизовывать. Имена, исполь- зуемые во внешних ссылках, можно заменять или исключать. Для облегчения редактирования обеспечивается автоматическая замена управляющих секций. Если две или более обрабатывае- мые управляющие секции имеют одинаковые имена, то только первая из них будет включена в загрузочный модуль. Другие будут исключены, и это не рассматривается как ошибка. Для обработки внешних ссылок редактор связей выполняет автома- тический библиотечный поиск в системных и пользовательских библиотеках. Однако пользователь может запретить такой по- иск для некоторых или даже для всех внешних ссылок. Редактор связей хранит в загрузочном модуле разнообраз- ную информацию, в том числе: дату трансляции (берется из объектной программы) и тип использовавшегося транслятора, а также даты редактирования и модификации (что позволяет иметь историю работы с загрузочными модулями). Когда ре- дактор связей помещает загрузочный модуль в библиотеку, то он заносит его имя в каталог. В каталоге о каждом модуле кроме имени хранится и другая информация, например,
166 Гл. 3. Загрузчики и программы связывания допускаем ли модуль повторную обработку редактором связей, имеет ли он оверлейную структуру, является ли он повторно вхо- димым или разделяемым и многие другие атрибуты. Некото- рые из этих атрибутов сообщаются пользователем, другие гене- рируются редактором связей в процессе работы. Редактор связей System/370 поддерживает оверлейные про- граммы, структура которых совпадает с той, что мы рассмат- ривали в разд. 3.3.3, и даже предоставляет более широкие воз- можности. В каждом сегменте разрешается использовать не- сколько входных точек. Для этого в каждый сегмент, который может потребовать оверлейную загрузку, автоматически вклю- чается таблица входов {entry table). В этой таблице для каж- дого внешнего имени указан соответствующий ему сегмент. Имеется также и таблица сегментов (она похожа на таблицу сегментов, описанную нами в разд. 3.3.3). В оверлейных про- граммах при некоторых ограничениях разрешено использовать так называемые исключительные ссылки (exclusive referen- ces)— ссылки, которые требуют оверлейного замещения самого вызывающего сегмента. Примером такой ссылки мог бы слу- жить вызов сегмента С из сегмента В в программе на рис. 3.12а. Однако пользоваться этой возможностью рекомен- дуется только в особых случаях. В стандартном режиме (если не задан соответствующий атрибут) такие ссылки рассматри- ваются редактором связей как ошибочные. Как уже говорилось в разд. 3.3.3, оверлейная загрузка осу- ществляется автоматически при вызове сегмента. Кроме того, программа может явным образом потребовать загрузку конкрет- ного сегмента (обратившись к операционной системе). Некото- рые версии операционной системы допускают продолжение исполнения сегмента, выдавшего запрос, в то время как про- изводится загрузка требуемого сегмента. Это позволяет совме- щать во времени процессы загрузки и исполнения программы. Естественно, что подобные параллельные операции необходимо тщательно планировать при проектировании оверлейной про- граммы. В System/370 оверлейные программы могут разбиваться на несколько различных областей, что позволяет более эффектив- но использовать оперативную память. Каждая область содер- жит древовидную оверлейную структуру. Внутри одной области применяются обычные правила организации оверлея. Однако области не зависят друг от друга, и поэтому сегмент одной области может свободно обращаться к любому сегменту другой области. Рассмотрим, например, структуру на рис. 3.18а. Это та же самая оверлейная структура, что и на рис. 3.12а, но с добав- ленными управляющими секциями L, М и N. Каждая из них может вызываться либо из секции Н, либо из секции J. Эта
3.5. Примеры реализации 167 Рис. 3.18. Оверлейная программа, расположенная в нескольких областях,
168 Гл. 3. Загрузчики и программы связывания структура является недопустимой, так как L, М и N размеще- ны в двух различных узлах дерева, что приводит к неоднознач- ному определению внешних имен. Для того чтобы организовать допустимую оверлейную структуру, расположенную в одной области, нам надо было бы разместить L, М и N в корневом сегменте. В этом случае они были бы доступны как Н, так и J (рис. 3.186). Однако это означало бы, что все эти три управ- ляющие секции оставались бы в оперативной памяти в течение всего времени исполнения программы. Таким образом, преиму- щество оверлейной структуры было бы утрачено. Если L, М и N велики по объему, то это привело бы к значительному увеличению размера занимаемой оперативной памяти. На рис. 3.18в эта проблема решена с помощью размеще- ния оверлейной структуры в нескольких областях. Область 1 содержит ту же самую оверлейную структуру, которая приве- дена на рис. 3.12а. Область 2 содержит сегменты L, М и N.. Эти сегменты подключены к фиктивному корню для того, что- бы подчеркнуть, что они могут друг друга замещать. Это по- зволяет решить проблему эффективного использования опера- тивной памяти и в то же время позволяет обращаться к любому сегменту Области 2 как из сегмента Н, так и из сег- мента J. Обычно в состав System/370 наряду с редактором связей входит и связывающий загрузчик. Загрузчик предоставляет меньше средств, чем редактор связей. Например, он не поддер- живает оверлейные программы и не создает загрузочные моду- ли, которые можно помещать в библиотеку. Однако, поскольку загрузчик является более простым и не создает загрузочные модули, он позволяет сократить примерно на половину время, требуемое на загрузку и связывание программы. Поэтому для повышения эффективности его рекомендуется применять всегда, когда не требуется выполнять специализированные функции редактора связей. Более подробную информацию о редакторе связей Sy- sterii/370 можно найти в IBM [1978] и IBM [1972а]. 3.5.2. Программа связывания ЭВМ VAX Редактор связей в системе VAX называется программой связывания {linker} и выполняет функции, аналогичные тем, что мы уже обсуждали в этой главе. Формат объектной програм- мы VAX несколько более сложный, чем формат, использовав- шийся для УУМ/ДС. Результатом работы программы связыва- ния является одна или несколько блок-секций {image sections). Блок-секция состоит из нескольких программных секций (PSECT), имеющих сходные значения атрибутов (например, таких как защита по записи или исполнению),
3.5. Примеры реализации 169 Работа программы связывания по созданию блок-секций управляется ассемблером или компилятором с помощью специ- альных команд, включаемых в объектную программу. (Запись- модификатор, являющаяся частью объектной программы УУМ/ДС, представляет собой простой пример команды этого типа.) В качестве рабочей памяти программа связывания ис- пользует внутренний стек. Команды объектной программы мо- гут заносить в стек разнообразную информацию, считывать значения из стека и записывать их в блок-секцию, а также вы- полнять операции над величинами, записанными в стек. Набор команд состоит более чем из 50 различных операций, обеспе- чивающих самые широкие возможности. На рис. 3.19 приведен простой пример, иллюстрирующий дан- ный способ организации перемещения и связывания. На рис. 3.19а показаны три программные секции, ассемблировав- шиеся совместно. По существу это те же программные секции, которые использовались нами в примере на рис. 3.7. Однако здесь показана только та часть, которая необходима для обра- ботки предложения с меткой REF4 (из секции PROGA). На рис. 3.196 приведен несколько упрощенный вариант команд, обеспечивающих генерацию значения предложения REF4. Первая команда заносит в вершину стека число 14 (шест- надцатеричное), являющееся значением выражения (ENDA — LISTA). Вторая команда заносит в стек базовый адрес сек- ции PROGC (он назначается программой связывания) плюс смещение 30 (шестнадцатеричное). Эта сумма представляет собой значение метки LISTC. Третья команда суммирует два верхних элемента стека и помещает результат в его вершину. Последняя команда запоминает эту величину в качестве сле- дующего слова генерируемой блок-секции. Это как раз то зна- чение, которое должно содержаться в слове с меткой REF4. Программа связывания VAX может генерировать три типа блок-секций. Блок-секции первого типа — это выполняемые сек- ции (executable images), используемые для загрузки и исполне- ния. Однако их последующая обработка программой связыв- ния невозможна. Второй тип блок-секций — это разделяемые секции (shareable images). Такие секции исполняться не могут, но зато они могут впоследствии обрабатываться программой связывания. Их можно, например, использовать в качестве про- межуточного представления для очень больших программ. Разделяемые блок-секции позволяют также различным программам использовать одну и ту же копию некоторого на- бора команд или областей данных. На магнитном диске и в Оперативной памяти присутствует только один экземпляр раз- деляемой блок-секции, что дает возможность экономить эти ресурсы.
170 Гл. 3. Загрузчики и программы связывания Адрес Исходное положение оооо PSECT PROGA 0040 LISTA. • • • l PSECT 1 0054 • ENDA • 7 (PROGA) • > REF4 * • WORD ENDA-LISTA+LISTC 0000 * ♦ • PSECT • • PROGB 4 [ PSECT 2 f (PROGB) €000 ♦ PSECT • PROGC 1 PSECT 3 0030 * LISTC • • • • > (PROGC) * > • END a 1 Команда операнд Объектный код и команды переме- щения/ связывания для предложения REF4 В PROQA Поместить значение в стек значение ~ И Поместить в стек базу PSECT PSECT « 3{PROGC}, плюс смещение смещение ~30 Сложение Запомнить слово 6 Рис. 3.19. Пример использования механизма перемещения и связывания ЭВМ VAX. Третий тип — это системные блок-секции (system images). Такая блок-секция предназначена для работы на машине без поддержки операционной системы. (Операционная система VAX сама является системной блок-секцией.) Эти блок-секции ис-
3.5. Примеры реализации 171 пользуются только для специальных целей. По сравнению с другими типами блок-секций системная блок-секция имеет бо- лее простую структуру. Программа связывания VAX выполняет обычные функции по перемещению и связыванию программ. В дополнение к это- му она делает часть работы, которая в других системах выпол- няется ассемблером или компилятором. Например, ассемблер VAX не собирает все части программных секций в единую объектную программу. Эта реорганизация осуществляется про- граммой связывания. Аналогично ссылки между различными программными секциями, ассемблируемыми совместно, обраба- тываются программой связывания под управлением последова- тельности команд сгенерированной ассемблером, и в отличие от многих других систем в системе VAX не требуется явного объявления внешних имен. (Это один из примеров того, на- сколько мощным является используемый в системе VAX способ командного управления связыванием.) Ссылки между раздельно ассемблируемыми программными секциями обрабатываются так, как было описано в разд. 3.2. Используемые имена дожны быть объявлены глобальными (т. е. внешними). Определения глобальных имен являются составной частью объектного представления и используются программой связывания для разрешения внешних ссылок между програм- мными секциями. Различают два типа внешних ссылок — с иль- ные (strong) и слабые (weak). Сильные ссылки обрабатываются обычным образом. Разрешение слабых ссылок осуществляется только в том случае, если секция, в которой определены соот- ветствующие имена, присутствует во входном потоке програм- мы связывания. Автоматический библиотечный поиск для этих ссылок не делается. Неразрешенные слабые ссылки не рассмат- риваются как ошибки. Вместо этого им присваивается нулевое значение. Одно из применений таких слабых ссылок — тестиро- вание модульных программ. В этом случае может возникнуть желание протестировать часть программы прежде, чем будут написаны все подпрограммы. Если все вызовы к отсутствую- щим подпрограммам идентифицировать как слабые ссылки, то программу можно будет беспрепятственно загрузить и испол- нять. Конечно, если программа попытается вызвать отсутствую- щую подпрограмму, то в результате будет зафиксирована ошибка. (Редактор связей System/370 и загрузчик ЭВМ CY- BER допускают использование похожих слабых ссылок.) Определения глобальных переменных обычно отсутствуют в исполняемой блок-секции, поскольку эта секция не предназна- чена для повторной обработки с помощью программы связы- вания. Однако в случае использования некоторых средств от- ладки глобальные имена остаются в исполняемой блок-секции и применяются при выводе диагностических сообщений. В раз-
172 Гл. 3. Загрузчики и программы связывания деляемой блок-секции определения некоторых глобальных имен сохраняются для того, чтобы обеспечить возможность связыва- ния этой секции с другими программами. Эти имена, называе- мые общими (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. перемещаемое значение находится в правой половине слова;
3.5. Примеры реализации 173 4. перемещаемые значения находятся в обеих половинах слова; 5. перемещаемое значение находится в середине 30-разряд- ного слова. Поэтому, когда используется аппарат масок перемещения, каждому слову сопоставляется 4-разрядное поле. Это поле ис- пользуется для кодирования перечисленных выше вариантов и для указания операции, которую надо выполнить для модифи- кации перемещаемых значений (либо прибавить базовый адрес к перемещаемому значению, либо вычесть). Загрузчик CYBER поддерживает оверлейные программы, но на их структуру накладываются более жесткие ограничения. Оверлейная структура ограничена максимум тремя уровнями. Сегмент идентифицируется упорядоченной парой целых чисел и может содержать только одну входную точку. Каждый сегмент, кроме корневого, загружается по явному запросу. Автоматиче- ская загрузка сегментов не предусмотрена. Прикладные про- граммы могут запросить загрузку сегмента, обратившись к опе- рационной системе. В качестве альтернативы допускается использование небольшого резидентного загрузчика, обеспечив вающего оверлейную загрузку. Этот загрузчик может быть включен в корневой сегмент. Имеется также и более мощное средство организации овер- лейного процесса, которое называется сегментацией (segmented Поп). Сегментированная программа также должна иметь дре- вовидную структуру, однако допускается использование более трех уровней и каждый сегмент может иметь несколько вход- ных точек. Разрешается, кроме того использовать оверлейные программы, размещенные в нескольких областях памяти. Такая организация оверлейной структуры похожа на то, что мы рас- сматривали для System/370 (хотя и используется другая тер- минология). Программист с помощью командного языка может явно задать древовидную структуру, указав, какие программы должны включаться в соответствующие сегменты. Если это не сделано, то загрузчик распределяет программы между сегмен- тами, исходя из анализа существующих между ними внешних связей. В дополнение к стандартным функциям загрузчик CYBER позволяет создавать так называемые капсулы (capsules). Кап- сула состоит из одной или нескольких объектных программ, связанных между собой и записанных в специальном формате, позволяющем осуществлять быструю загрузку. Для того чтобы загрузить капсулу в оперативную память, прикладные програм- мы могут вызвать быстрый динамический загрузчик (Fast Dy- namic Loader — FDL). Загрузка с помощью FDL более эф- фективна, чем другие методы загрузки, и требует меньшил
I74 Гл. 3. Загрузчики и программы связывании ресурсов оперативной памяти, однако она накладывает более жесткие ограничения и сложнее в использовании. Дополнительную информацию о загрузчике CYBER можно найти в CDC [19826]. Упражнения Раздел 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. В некоторых загрузчиках используется косвенная схема связывания. Для того чтобы использовать подобную схему в УУМ/ДС, ассемблер дол-
Упражнения 175 жен был бы сгенерировать список указателей для всех имен, перечисленных в предположениях EXTREF. (Один указатель на каждое внешнее имя.) С помощью записей-модификаторов загрузчику было бы предписано занести значения адресов соответствующих внешних имен в эти указатели. Тогда внешние ссылки можно было бы осуществлять посредством косвенной адре- сации через эти указатели. Так, например, команда вида LDA XYZ (где XYZ — внешняя ссылка) ассемблировалась бы так, как если бы она имела вид LDA @PXYZ где PXYZ — указатель на XYZ. В чем преимущества и недостатки этого метода? 9. Предложите проект однопросмотрового связывающего загрузчика. Ка- кие ограничения (если они нужны) требуется наложить в этом случае? В чем преимущества и недостатки такого однопросмотрового загрузчика? 10. Некоторые языки программирования разрешают размещать данные в общих областях памяти !). Исходная программа может содержать не- сколько общих областей (с разными именами). Мы можем рассматривать каждую общую область как отдельную управляющую секцию. При связывании и загрузке программ общим областям с одинаковыми именами присваивается один и тот же начальный адрес. (Эти общие об- ласти в разных программах могут иметь различную длину.) Таким образом, устанавливается соответствие между переменными общих областей, объяв- ленными в различных программах. Любые данные, записанные в общую область одной программой, становятся доступными для других. Как мог бы загрузчик обрабатывать такие общие области? (Предло- жите способ модификации алгоритма на рис. 3.10 для обеспечения работы с общими областями.) Раздел 3.3 ! 1. Модифицируйте алгоритм на рис. 3.10 так, чтобы он для разрешения внешних ссылок обеспечивал автоматический библиотечный поиск. Вы мо| жете считать, что доступ к библиотекам осуществляется с помощью сер! висных процедур операционной системы. 2. Модифицируйте алгоритм па рис. 3.10 так, чтобы он позволял обра* батывать команды CHANGE, DELETE и INCLUDE, описанные в разд. 3.3.24 Если потребуется, вы можете ввести необходимые ограничения на их ис* пользование. 3. Предположим, что загрузчик готовит листинг, который включает по только адреса, присвоенные внешним именам, но и таблицу перекрестны?; ссылок между загружаемыми управляющими секциями. Какая информация может быть полезна в таком листинге? Коротко опишите способ реализации этой возможности и дайте определения необходимых для этого структур данных. 4. Рассмотрим оверлейную структуру, показанную на рис. 3.12а. Пред- положим, что подпрограмма Н может быть вызвана не только из В, нс/ и из С. Как это можно реализовать в оверлейной программе? (Считаем, что правила оверлея, определенные в разд. 3.3.3, не меняются.) 5. Опишите алгоритм назначения начальных адресов сегментам ^овер- лейной структуры, принимая во внимание объем памяти, необходимый для размещения SEGTAB и OVLMGR. ° Имеются в виду области типа COMMON языка Фортран. Прим. ре&
176 Гл. 3. Загрузчики и программы связывания 6. Во время выполнения оверлейной программы существует много раз- личных наборов сегментов, которые могут находиться в памяти в различ- ные моменты времени. На рис. 3.146 показаны три возможных варианта для программы на рис. 3.12а. Приведите другие варианты, используя ана- логичную форму представления. 7. Дайте краткое описание процедуры управления оверлеем, которую мы назвали OVLMGR. Определите необходимые для нее структуры данных. 8. Предположим, что мы хотим разрешить иметь в сегментах оверлей- ной программы более чем одну входную точку. Например, в программе на рис. 3.12а мы хотели бы непосредственно из сегмента А обращаться либо к входу D, либо к входу Е. При этом независимо от того, по какому входу будет сделан вызов, должен загружаться весь сегмент D/Е. Каким образом может быть реализована такая возможность — загрузчиком и (или) ме- неджером оверлея? 9. В описанном нами оверлейном процессе только передачи управления к другому сегменту вызывают оверлейную загрузку. Как можно было бы реализовать оверлейный процесс так, чтобы ссылка на данные также приво- дила к оверлейной загрузке? Вы можете ввести любые, необходимые по вашему мнению, ограничения. Раздел 3.4 1. Определите формат модуля, подходящий для представления связан- ной программы, генерируемой редактором связей. Будем считать, что свя- занная программа не предназначена для повторной обработки с помощью редактора связей. Опишите алгоритм перемещающего загрузчика, который можно использовать для загрузки модулей данного формата. 2. Рассмотрим следующие варианты хранения, связывания и исполне- ния пользовательской программы. а) Хранится только исходная программа; при каждом запуске про- граммы она переассемблируется и загружается связывающим загрузчиком., б) Хранятся исходная программа и ее объектное представление; при каждом запуске программы она загружается с помощью связывающего за- грузчика. в) Хранятся исходная программа и вариант программы, подготовленный редактором связей, в котором оставлены неразрешенными ссылки на биб- лиотечные подпрограммы; при каждом запуске программы она загружается с помощью связывающего загрузчика. г) Хранятся исходная программа и вариант программы, подготовлен- ный редактором связей, в котором разрешены все внешние ссылки; при каждом запуске программы она загружается с помощью связывающего за- грузчика. д) Хранятся исходная программа и вариант программы, подготовлен- ный редактором связей, в котором разрешены все внешние ссылки и вы- полнена настройка относительных адресов; при каждом запуске программы она загружается с помощью абсолютного загрузчика. Укажите условия, при которых целесообразно использовать каждый из этих вариантов. Предполагается, что в исходной программе не делается никаких изменений. 3. Динамическое связывание и оверлейная загрузка имеют много общих черт. Сопоставьте эти два подхода с точки зрения эффективности, простоты использования и других важных, на ваш взгляд, факторов. В каких случаях каждый из этих подходов предпочтительнее другого? 4. Динамическое связывание, как оно было описано в разд. 3.4.2, ра- ботает только для передачи управления, Как его можно приспособить для
Упражнения 177 того, чтобы оно обеспечивало динамическую загрузку и при ссылках на данные? 5. Предположим, что подпрограмма, загруженная с помощью динами- ческой загрузки, должна оставаться в оперативной памяти до окончания главной программы. Предложите средства повышения эффективности ди- намического связывания за счет того, что операционная система будет ис- пользоваться только для загрузки программы, но не для передачи на нее управления. 6. Предположим, что требуется удалить из памяти динамически загру- женную подпрограмму (для повторного использования пространства памя- ти). Будет ли предложенный вами в упр. 5 метод работать в этом случае? Какие возникают здесь проблемы и как они могут быть разрешены? 7. Предположим, что нажатие на пульте УУМ/ДС кнопки «старт си- стемы» вызывает чтение с вводного устройства 128-разрядной записи, ко- торая размещается в оперативной памяти, начиная с адреса 0000. После того как запись прочитана, управление автоматически передается по адре- су 0000. Какие команды должны находиться в первой раскручивающей записи для того, чтобы можно было загрузить следующую за ней абсолют- ную объектную программу? Для раскручивающей записи и объектной про- граммы вы можете выбрать любой удобный вам формат,
Глава 4. Макропроцессоры В этой главе мы рассмотрим функции и способы реализации макропроцессоров. Макроинструкции представляют собой удоб- ное средство записи часто используемых групп предложений исходного языка программирования. Процесс замены макроин- струкций соответствующими группами предложений, осущест- вляемый макропроцессором, называется макрорасширением, расширением макроинструкций или макрогенерацией. Таким об- разом, макроинструкции позволяют программисту записать компактный вариант своей программы, оставляя все техниче- ские детали, связанные с получением окончательного текста, макропроцессору. Основной функцией макропроцессора является замена одних групп символов или строк на другие. За исключением некото- рых специальных случаев, макропроцессор не анализирует смысл обрабатываемого им текста. На устройство и возмож- ности макропроцессора может повлиять форма предложений используемого языка программирования; смысл этих предложе- ний и вопросы трансляции их в машинные коды не имеют не- посредственного отношения к процессу макрогенерации. Поэто- му механизм работы макропроцессора практически не связан со структурой машины, на которой он должен работать. Наиболее часто макропроцессоры применяются при про- граммировании на языке ассемблера. Мы также будем исполь- зовать язык ассемблера машины УУМ для иллюстрации боль- шинства обсуждаемых концепций. Надо, однако, иметь в виду, что макропроцессоры могут быть также полезны при програм- мировании на языках высокого уровня, на командных языках операционной системы и т. д. Кроме того, имеются макропро- цессоры общего назначения, которые не связаны непосредствен- но с каким-либо конкретным языком. В последующих разделах этой главы мы кратко обсудим эти более общие случаи исполь- зования макросредств. В разд. 4.1 вводятся основные понятия, включая «макро- определение» и «макрорасширение». В нем также содержится описание алгоритма работы простого макропроцессора. В разд. 4.2 обсуждаются более сложные возможности, которы- ми обладают многие макропроцессоры: порождение уникальных меток, условная макрогенерация и использование ключевых па- раметров в макроопределениях. Все эти возможности являются
4.1. Основные функции макропроцессоров 179 машинно-независимыми. Поскольку устройство макропроцессо- ров не связано непосредственно со структурой ЭВМ, эта глава не содержит раздела, посвященного машинно-зависимым осо- бенностям макропроцессоров. В разд. 4.3 описаны некоторые дополнительные возможности макропроцессоров. Одна из них (рекурсивная макрогенерация) оказывается связанной с внутренней структурой макропроцес- сора; другие относятся к связям макропроцессора с такими компонентами системного обеспечения, как ассемблер или ком- пилятор. Заключительный разд. 4.4 содержит краткое описание трех реальных макропроцессоров. Один из них является макропро- цессором общего назначения, а два других предназначены для использования при программировании на языке ассемблера. В главе приводятся ссылки на литературу, содержащую другие примеры макропроцессоров. 4.1. Основные функции макропроцессоров В этом разделе мы рассмотрим общие для всех макропро- цессоров функции. В разд. 4.1.1 обсуждаются макроопределе- ния, процессы макровызова и макрогенерации с учетом пара- метров. В иллюстрирующих примерах используется язык ассемблера УУМ/ДС. В разд. 4.1.2 описываются однопросмот- ровый алгоритм работы простого макропроцессора и необходи- мые для его работы структуры данных. В последующих разделах этой главы обсуждаются вопросы реализации некоторых допол- нительных возможностей. 4.1.1. Макроопределения и макрорасширения На рис. 4.1 изображен пример программы для УУМ/ДС, в которой используются макроинструкции. Эта программа выпол- няет те же функции и имеет ту же логику, что и фрагмент программы, изображенной на рис. 2.5. Изменен только способ нумерации предложений. В этой программе определены и используются две макроин- струкции RDBUFF и WRBUFF. Функции и логика макро- определения RDBUFF те же, что и у подпрограммы RDREG, изображенной на рис. 2.5; макроопределение WRBUFF ана- логично подпрограмме WRREG. Эти макроопределения находят- STARTeX°^°** пР0ГРамме непосредственно после предложения В них используются две новые директивы ассемблера — MACRO и MEND. Первое предложение MACRO (строка 10) идентифицирует начало макроопределения. Текст в поле метки
180 Гл. 4. Макропроцессоры COPY START 0 КОПИРОВАНИЕ ФАЙЛА 10 RDBUFF MACRO MNDEV,4BUFADR,4RECLTH 15 20 . МАКРОС ДЛЯ ЧТЕНИЯ В БУФЕР 25 а 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 40 CLEAR S 45 +LDT ♦4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 TD =X'6INDEV' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ *-3 ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD -X'&INDEV' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 65 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ *+11 ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH 6BUFADR,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT *-19 ДЛИНЫ ЗАПИСИ 90 STX &RECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 95 MEND 00 WRBUFF MACRO 40UTDEU,&BUFADR,4RECLTH 105 а 110 л МАКРОС ДЛЯ ЗАПИСИ ИЗ БУФЕРА 115 к 120 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 125 LDT &RECLTH 130 LDCH &BUFADR,X ВЫБОРКА СИМВОЛА ИЗ БУФЕРА 135 TD =X'60UTDEV' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 140 JEQ *-3 ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 145 WD -X'&OUTDEV' ЗАПИСЬ СИМВОЛА 150 TIXR T ЦИКЛ ПОКА ВСЕ СИМВОЛЫ НЕ БУДУТ 155 JLT *-14 ЗАПИСАНА 160 MEND 165 170 » ОСНОВНАЯ ПРОГРАММА 175 180 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА 190 CLOOP RDBUFF Fl,BUFFER.LENGTH ЧТЕНИЕ ЗАПИСИ В БУФЕР 195 LDA LENGTH ПРОВЕРКА НА КОНЕЦ ФАЙЛА 200 COMP ♦0 205 JEQ ENDFIL ВЫХОД ПО КОНЦУ ФАЙЛА 210 WRBUFF 05,BUFFER,LENGTH ЗАПИСЬ ВЫХОДНОЙ ЗАПИСИ 215 J CLOOP ЦИКЛ 220 ENDFIL WRBUFF 05,EOF,THREE ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА 225 J QRETADR 230 EOF BYTE C'EOF' 235 THREE WORD 3 240 RETADR RESW 1 245 LENGTH RESW 1 ДЛИНА ЗАПИСИ 250 BUFFER RESB 4096 БУФЕР ДЛИНОЙ 4094 БАЙТ 255 END FIRST Рис. 4.1. Использование макросов в программе для УУМ/ДС. (RDBUFF) является именем этого макроопределения* В поле операнда находятся имена формальных параметров мак- роопределения. В нашем макроязыке имена всех формальных параметров начинаются спецсимволом & (амперсанд), что по- зволяет осуществлять замену формальных параметров на фак- тические в процессе макрогенерации, Имя и параметры макро-
4.1. Основные функции макропроцессоров 181 са определяют шаблон или прототип используемой програм- мистом макроинструкции. После директивы 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. Строки 190а ~ 1901 представляют собой полное расширение инструкции макроинициализации в строке 190. Строки коммен- тариев внутри тела макроопределения удалены, однако коммен- тарии внутри предложений языка остались. Обратите внима- } Этот введенный автором термин не является общепринятым в нашей литературе, так же, впрочем, как и термин «макропрототип», обозначаю* щии заголовок макроопределения. — Прим. ред.
182 Гл. 4. Макропроцессоры 5 COPY START 0 КОПИРОВАНИЕ ФАЙЛА 180 FIRST STL RETADR ЗАПОМИНАНИЕ АДРЕСА ВОЗВРАТА 190 .CLOOP RDBUFF Fl,BUFFER,LENGTH ЧТЕНИЕ ЗАПИСИ В БУФЕР 190а CLOOP CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 190b 190с 190d CLEAR CLEAR +LDT A S 04096 УСТАНОВКА МАКС. ДЛИНЫ ЗАПИСИ 190е TD *=X'F1' ПРОВЕРКА ВХОДНОГО УСТРОЙСТВА 190Г JEQ *-3 ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 1909 RD -XZF1Z ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 190h COMPR A,S ПРОВЕРКА ОКОНЧАНИЯ ЗАПИСИ 190i JEQ *+11 ВЫХОД ПО КОНЦУ ЗАПИСИ 190J STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 190k TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ 1901 JLT *-19 МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 190м STX LENGTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 195 LDA LENGTH ПРОВЕРКА НА КОНЕЦ ФАЙЛА 200 205 COMP JEQ <0 ENDFIL ВЫХОД ПО КОНЦУ ФАЙЛА 210 WRBUFF «5,BUFFER,LENGTH ЗАПИСЬ БУФЕРА 210а CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 210b 210с LDT LDCH LENGTH BUFFERrX ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 2104 TD *=X'05' ПРОВЕРКА ВЫХОДНОГО УСТРОЙСТВА 210е JEQ *-3 ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 210Г WD -X'05z ЗАПИСЬ СИМВОЛА 2109 TIXR T ЦИКЛ, ПОКА ВСЕ СИМВОЛЫ НЕ 210h JLT *-14 БУДУТ ЗАПИСАНЫ 215 J CLOOP ЦИКЛ 220 •ENDFIL WRBUFF 05rEOF,THREE ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА 220а ENDFIL CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 220b 220с LDT LDCH THREE EOF,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА 2204 TD *X'05' ПРОВЕРКА ВЫХОДНОГО УСТРОЙСТВА 220е JEQ *-3 ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 220Г WD «=XZ05/ ЗАПИСЬ СИМВОЛА 2209 TIXR T ЦИКЛ, ПОКА ВСЕ СИМВОЛЫ НЕ 220h JLT *-14 БУДУТ ЗАПИСАНЫ 225 230 EOF J BYTE GRETADR C'EOF' 235 THREE WORD* 3 240 RETADR RESW 1 245 LENGTH RESW 1 ДЛИНА ЗАПИСИ 250 BUFFER RESB 4096 БУФЕР ДЛИНОЙ 4096 БАЙТ 255 Рис. 4.2. END FIRST Программа на рис. 4.1 с макрорасширениями. ние, что сами предложения макроинициализации вставлены как строки комментария. Они служат для напоминания об исход- ных предложениях, написанных программистом. Метка предло- жения макроинициализации (CLOOP) оставлена в качестве метки первого предложения сгенерированного макрорасшире- ния. Это позволяет программисту использовать макроинструк- ции аналогично другим инструкциям языка ассемблера. Расши- рение предложений макроинициализации в строках 210 и 220 осуществляется аналогично. Заметьте, что в двух макроинициа- лизациях макроса WRBUFF заданы разные аргументы и в ре- зультате получены различные расширения.
4.1. Основные функции макропроцессоров 183 После обработки макропроцессором расширенный файл (рис. 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. Макропроцессор. Таблицы и логика Легко себе представить двухпросмотровый макропроцессор, в котором все макроопределения обрабатываются при первом просмотре, а все предложения макроинициализации — при вто- ром. Однако такой двухпросмотровый макропроцессор не допу^
184 Гл. 4. Макропроцессоры 1 2 MACROS RDBUFF MACRO MACRO (стандартная версия макросов для УУМ) &INDEV,&BUFADR,&RECLTH M 9 (стандартная версия для УУМ) 3 4 WRBUFF MEND MACRO (конец RDBUFF) &OUTDEV г &BUFADR, IcRECLTH w {стандартная версия ДЛЯ УУМ) 5 MEND (конец WRBUFF) M 6 MEND (конец MACROS! а 1 2 MACROX RDBUFF MACRO MACRO (версия макросов для УУМ/ДС) &INDEV, &BUFADR, &RECLTH M M* (версия для УУМ/ДС) 3 4 WRBUFF MEND MACRO (конец RDBUFF) iOUTDEVx&BUFADR^RECLTH M ж (версия для УУМ/ДС) 5 MEND {конец WRBUFF) 6 » w MEND (конец MACROX) б. Рис. 4.3. Пример использования макроопределения внутри тела другого мак- роопределения. скает наличия в теле одного макроопределения определений других макросов (поскольку все макроопределения должны быть обработаны при первом просмотре до выполнения любых макрорасширений). Подобного рода определение одних макросов с помощью других может быть иногда полезно. Рассмотрим в качестве примера два макроопределения на рис. 4.3. Тело первого мак- роса (MACROS) содержит предложения, определяющие RDBUFF, WRBUFF и другие макроинструкции для УУМ (рис. 4.3а). Тело другого макроопределения (MACROX) со- держит определения тех же макросов для УУМ/ДС (рис. 4.36). Программа, предназначенная для использования на стандартной УУМ, может инициализировать MACROS для опре- деления необходимых ей макросов. Программа для УУМ/ДС может инициализировать MACROX для определения тех же макросов в расширенной ДС версии, Таким образом, одна и
4.1. Основные функции макропроцессоров 185 та же программа сможет работать как на стандартной УУМ, так и на УУМ/ДС (используя все ее дополнительные возмож- ности). Единственное, что для этого надо сделать,— это иници- ализировать один из макросов: либо MACROS, либо MACROX. Важно понять, что само описание макроопределений MACROS или MACROX не определяет RDBUFF и других макросов. Они становятся доступными для использования только после завер- шения обработки предложений макроинициализации макросов MACROS или MACROX 1>. Однопросмотровый макропроцессор, который может пере- ключаться с обработки макроопределений на выполнение мак- рорасширений и наоборот, способен обрабатывать макроопреде- ления, подобные изображенным на рис. 4.3. В этом разделе мы рассмотрим алгоритм работы и структуры данных такого макропроцессора. Поскольку макропроцессор предполагается однопросмотровым, любые макроопределения должны появлять- ся в исходной программе прежде соответствующих предложе- ний макроинициализации. Это ограничение практически не оборачивается какими-либо неудобствами для программиста, поскольку появление предложения макроинициализации до со- ответствующего макроопределения смутит любого знакомяще- гося с такой программой. В нашем макропроцессоре используются три основные струк- туры данных. Сами макроопределения находятся в таблице макроопределений (DEFTAB), которая содержит макропрото- типы и предложения, составляющие тело макроопределений (с некоторыми модификациями). Строки комментариев, содержа- щиеся внутри макроопределений, отсутствуют в таблице DEFTAB, поскольку они не нужны на этапе макрогенерации. Для повышения эффективности процесса подстановки аргумен- тов ссылки на формальные параметры макроопределений пре- образованы в их порядковые номера. Имена макросов содер- жатся в таблице NAMTAB, которая, по существу, является таблицей указателей на DEFTAB. Для каждого макроопреде- ления таблица NAMTAB содержит указатели на начало и ко- нец макроопределения, содержащегося в DEFTAB. Третья структура данных — это таблица аргументов (ARG- TAB), заполняемая на этапе обработки предложений макро- инициализации. Как только такое предложение распознано, его аргументы записываются в таблицу ARGTAB в соответствии с их номерами в списке аргументов. В процессе макрогенерации аргументы из таблицы ARGTAB заменяют собой соответствую- щие формальные параметры в теле макроопределения. По-видимому автор здесь хочет сказать, что одну и ту же про- грамму можно выполнять на УУМ и на УУМ/ДС, меняя только макро- определения. — Прим. pedt
186 Гл. 4. Макропроцессоры NAMTAB DEFTAB • • • • • • • • RDBUFF &INDEVДВЦГАРК,&RECLTH CLEAR X CLEAR A CLEAR 5 i +LDT #4096 ID JEQ *-3 RD COMPR A,S JEQ *+11 STCH ?2,X TIXR T JLT *-19 STX ?3 MEND RDBUFF ► 4 • ♦ • • ARGTAB 1 F1 2 BUFFER 3 LENGTH 6 Рис. 4.4. Содержимое таблиц макропроцессора для программы на рис. 4.1: а — фрагменты таблиц NAMTAB и DEFTAB, связанные с макроопределе- нием RDBUFF, б — содержимое таблицы ARGTAB для макроинициализации RDBUFF в строке 190. На рис. 4.4 показаны фрагменты содержимого этих таблиц на этапе обработки программы, изображенной на рис. 4.1. Рис. 4.4а содержит макроопределение RDBUFF, записанное в таблицу DEFTAB, на начало и конец которого смотрят соот- ветствующие указатели из таблицы NAMTAB. Обратите внимание на позиционную нотацию, использованную для обо- значения параметров: параметр &INDF превращен в ?1 (ука- зывая на то, что это первый параметр в прототипе), параметр &BUFADR преобразован в ?2 и т. д. На рис. 4.46 изображе- на таблица ARGTAB в том виде, в котором она окажется во время расширения макроса RDBUFF в строке 190. Для этой макроинициализации первым аргументом будет F1, вторым — BUFFER и т. д. Такая запись параметров делает процесс их замены на соответствующие аргументы существенно более эф- фективным. Как только встречается конструкция ?п в строке
4.1. Основные функции макропроцессоров 187 таблицы DEFTAB, за счет простых индексных операций можно выбрать соответствующий аргумент из таблицы ARGTAB. Собственно алгоритм работы макропроцессора представлен на рис. 4.5. Процедура DEFINE, которая вызывается, как толь- ко распознано начало макроопределения, формирует соответ- ствующие строки таблиц DEFTAB и NAMTAB. Процедура EXPAND записывает аргументы в таблицу ARGTAB и осуще- ствляет расширение предложений макроинициализации. Про- цедура GETLINE, которая вызывается в нескольких местах ал- горитма, выбирает очередную строку для обработки. Это может быть строка из таблицы DEFTAB (очередная строка тела мак- роопределения) или очередная строка входного файла в зави- симости от того, какое значение имеет булевская переменная EXPANDING. Одна из особенностей этого алгоритма заслуживает даль- нейших пояснений. Это обработка макроопределений внутри тела других макроопределений (как на рис. 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, такого рода возможностью.
begin (макропроцессор) EXPANDING r FALSE while OPCODE < > 'END' do begin GETLINE PROCESSLINE end (while) end (макропроцессор) procedure PROCESSLINE begin поиск OPCODE в NAMTAB if нашли then EXPAND else if OPCODE - 'MACRO7 then DEFINE else записать исходную строку в файл макрорасширения end (PROCESSLINE} procedure DEFINE begin записать имя. макроопределения в NAMTAB записать макропрототип в DEFTAB LEVEL := 1 while LEVEL > 0 do begin GETLINE if это не строка-комментарий then begin заменить вхождение имени к-ro параметра на занести строку в DEFTAB if OPCODE « 'MACRO' then LEVEL :=s LEVEL + 1 else if OPCODE » 'MEND' then LEVEL := LEVEL - 1 end ( если не строка-комментарий) end {while) записать в NAMTAB указатели на начало и конец макроопределения end (DEFINE) procedure EXPAND begin EXPANDING := TRUE взять первую строку макроопределения {заголовок) из DEFTAB записать аргументы макроинициализации в ARGTAB записать предложение макроинициализации как комментарий while не конец макроопределения do begin GETLINE PROCESSLINE end (while) EXPANDING ;=; FALSE end (EXPAND) procedure GETLINE begin if EXPANDING then begin взять следующую строку макроопределения из DEFTAB заменить выражения ?к на аргументы из ARGTAB end <if) else прочитать очередную строку входного Файла end (GETLINE)
4.2. Машинно-независимые особенности макропроцессора 189 4.2. Машинно-независимые особенности макропроцессора В этом разделе мы обсудим некоторые расширения описан- ных выше базовых функций макропроцессора. Как мы уже от- мечали, эти расширения не связаны непосредственно с архитек- турой машины, для которой создавался макропроцессор. В разд. 4.2.1 описан метод конкатенации параметров макроин- струкции с другими строками символов. В разд. 4.2.2 обсужда- ется один из методов генерации уникальных меток, который позволяет избежать частого использования относительной адре- сации в теле макроопределения. В разд. 4.2.3 вводится и иллюстрируется на нескольких примерах важное понятие ус- ловного макрорасширения (условной макрогенерации). Возмож- ность изменения макрорасширения путем использования управ- ляющих предложений делает макроинструкции существенно бо- лее мощным и полезным для программиста средством. В разд. 4.2.4 обсуждается определение и использование ключе- вых параметров макроинструкций. 4.2,1. Конкатенация макропараметров Большинство макропроцессоров допускает конкатенацию па- раметров с другими строками символов. Предположим, напри- мер, что программа содержит набор переменных с именами ХА1, ХА2, ХАЗ, ..., другой набор переменных с именами ХВ1, ХВ2, ХВЗ, ... и т. д. Если над подобными наборами переменных необходимо осуществить одинаковые действия, программист мо- жет записать их в виде макроопределения. Его параметр дол- жен так определить необходимый набор переменных (например, А, В и т. д.), чтобы макропроцессор смог, используя значения этого параметра, сконструировать в процессе макрогенерации все необходимые имена (ХА1, ХВ1 и т. д.). Предположим, что такой параметр назван &ID. Тело макро- определения может содержать предложение вида LDA X&ID1 в котором параметр &ID должен быть сконкатенирован со строкой, состоящей из символа X, расположенной до параметра, и со строкой, состоящей из символа 1, расположенной после параметра. Проблема состоит в том, что конец параметра ни- как специально не обозначен (его начало легко может быть идентифицировано по символу &). Таким образом, рассматри- ваемое предложение может быть интерпретировано также как Рис. 4.5. Алгоритм работы однопросмотрового макропроцессора.
190 Гл. 4. Макропроцессоры строка символов X, за которой следует параметр &ID1. В дан- ном конкретном случае макропроцессор сможет интерпретиро- вать это предложение правильно. Однако если макроопределе- ние содержит в качестве параметров и &ID, и &IDI, то ситуация 1 SUM 2 LDA 3 ADD 4 ADD 5 STA 6 MEND MACRO MT X^ID-^t XMD->2 X&ID^S X&ID^S SUM A 4 LDA XA1 ADD XA2 ADD XA3 STA XAS SUM BETA 4 LDA XBETA1 ADD XBETA2 ADD XBETA3 STA XBETAS в Рис. 4.6. Конкатенация макро- параметров» становится в принципе неразреши- мой. Большинство макропроцессоров решает эту проблему введением специального оператора конкатена- ции. В макроязыке УУМ этот опе- ратор записывается как Преды- дущее предложение на этом мак- роязыке должно будет иметь вид LDA X&ID -> 1 где конец параметра &ID четко определен. Макропроцессор уничто- жает все вхождения символа, обо- значающего оператор конкатена- ции, сразу же после завершения подстановки параметров. Таким об- разом, символ не появится в по- рожденном тексте макрорасшире- ния. На рис. 4.6а изображено макро- определение, в котором использу- ется оператор конкатенации. На рис. 4.66 и 4.6в изображены пред- ложения макроинициализации и со- ответствующие им тексты макрорас- ширения. Для того чтобы убедиться в правильном понимании работы оператора конкатенации, вы должны проследить весь процесс порождения этих макрорасширений. Вы можете также подумать о том, каким образом оператор конкатенации мог бы обрабатываться в алгоритме работы макропроцессора, подоб- ном изображенному на рис. 4.5. 4.2.2. Генерация уникальных меток Как мы уже говорили в разд. 4.1, тело макроопределения, вообще говоря, не должна срдержать обычных меток. Это при- водит к необходимости использования в исходной программе относительной адресации. Рассмотрим в качестве примера опре- деление WRBUFF на .рис. 4.1. Если бы команда TD в строке .135 была помечена, эта метка оказалась бы дважды определен- ной (для каждой инициализации WRBUFF). Это, естественно, не позволило бы ассемблеру получить готовую программу»
4.2. Машинно-независимые особенности макропроцессора 191 25 RDBUFF MACRO &INDEV,&BUFADR,&RECLTH зе CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 .CLEAR A 40 CLEAR S 45 +LDT ♦4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 $L3CP TD =X4lNDEV' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $LOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 40 RD =X'&INDEV' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 45 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ $EXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH 4BUFADR,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SLOOP ДЛИНЫ ЗАПИСИ 90 JEXIT STX 6RECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 95 MEND а RDBUFF FlгBUFFER,LENGTH 30 CLEAR X ОЧИСТКА СЧЁТЧИКА ЦИКЛА 35 CLEAR A 40 CLEAR S 45 +LDT ♦4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 ^AALOOP ТВ «X'Fl' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ SAALOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD *X'FT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 65- COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ $AAEXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SAALOOP ДЛИНЫ ЗАПИСИ 90 ФААЕХ1Т STX 6RECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 6 Рис. 4.7. Генерация уникальных меток для макрогенерации. Поскольку строку 135 в этом макроопределении нельзя по- метить, в командах перехода на строки 140 и 155 появляются относительные адреса *—3 и *—14. Использование относи- тельной адресации в исходной программе еще может быть приемлемым для коротких переходов типа JEQ *—3. Однако для переходов к далеко расположенным командам подобная запись является неудобной, увеличивает вероятность появления ошибок, трудна для понимания. Многие макропроцессоры реша- ют эту проблему за счет макрогенерации меток специального вида. Рис. 4.7 иллюстрирует один из способов порождения уни- кальных меток. Макроопределение RDBUFF изображено на рис. 4.7а. Метки, используемые внутри тела макроопределения, начинаются специальным символом $. На рис. 4.76 изображены предложения макроиницализации и соответствующие им текс-
192 Гл. 4. Макропроцессоры ты макрорасширений. Все идентификаторы, начинающиеся сим- волом $, модифицированы путем замены символа $ на символы $АА. При обработке других предложений макроинциализа- ции символ^ будет заменен на $ХХ, гдеХХ — двухсимвольный алфавитно-цифровой счетчик количества обработанных предло- жений макроинициализации. При обработке первого встретив- шегося в программе предложения макроинициализации XX будет иметь значение АА. При обработке последующих пред- ложений макроинициализации XX будет иметь значения АВ, АС и т. д. (Если для счетчика XX используются только латин- ские буквы и цифры, то подобный двухсимвольный счетчик по- зволяет обработать в одной программе до 1296 предложений макроинициализации.) Таким образом, в макрорасширениях, соответствующих различным предложениям макроинициализа- ции, метки будут различаться. Другие примеры будут изобра- жены на рис. 4.8 и 4.10. Язык ассемблера УУМ допускает появление символа $ в индентификаторах. Однако программисты предупреждаются о том, что этот символ не следует использовать в их программах. Это позволяет избежать всяких конфликтов между идентифи- каторами программиста и идентификаторами, порожденными макропроцессором. 4.2.3. Условные макрорасширения Во всех рассмотренных нами примерах использования маю роинструкций каждая инициализация некоторого макроса рас- ширялась всегда в одну и ту же последовательность предложе- ний. Эти предложения могли различаться за счет подстановки разных параметров. Однако их форма и последовательность всегда были неизменными. Даже такие простые макросредства могут быть весьма полезны. Но большинство макропроцессоров может изменять также последовательность порожденных в про- цессе макрогенерации предложений (в зависимости от значений аргументов в предложении макроинициализации). Подобная возможность существенно увеличивает мощь и гибкость макро- языка. В этом разделе мы рассмотрим типичный набор пред- ложений условной макрогенерации. Другие примеры содержатся в разд. 4.4, посвященном описанию некоторых макропроцес- соров. В связи с обсуждаемыми в этом разделе возможностями часто используется термин «условное ассемблирование». Суще- ствуют, однако, приложения макропроцессоров, никак не свя- занные с программированием на языке ассемблера. Поэтому мы предпочитаем термин «условная макрогенерация». На рис. 4.8 приведен пример использования одного из пред- ложений условной макрогенерации, На рис, 4f8a изображено
4.2. Машинно-независимые особенности макропроцессора 193 макроопределение RDBUFF логика и функции которого уже обсуждались. Это макроопределение содержит два дополни- тельных параметра: &EOR, который определяет шестнадцате- ричный код, являющийся признаком конца записи, и &MAXLTH, который определяет максимальную длину записи, которая может быть прочитана. (Как мы увидим, любой из этих параметров или оба сразу могут быть опущены при ини- циализации макроса RDBUFF.) Предложения в строках 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 повторить исходное сравнение, в результате 7 Зак. 792
194 Гл. 4. Макропроцессоры 25 RDBUFF MACRO 6INDEU,«.BUFADR,iRECLTH,}IE0R,4HAXLTH 26 IF (&EOR NE ' 27 4E0RCK SET 1 28 ENDIF 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 38 IF (&EORCK EG 1> 40 LDCH -X,6E0R/ УСТАНОВКА СИМВОЛА E0R 42 RMO A,S 43 ENDIF 44 IF (XMAXLTH £0 ") 45 +LDT ♦4096 МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ » 4Ш 46 ELSE 47 +LDT ♦4MAXLTH УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 48 ENDIF 50 $LOOP TD ₽X'6INDEU' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $LOOP ЦИКЛ ОЖИДАНИЯ готовности 60 RD s=X'4.INDEV' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 63 IF <6E0RCK EQ 1> 65 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEG $EXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH ABUFADR,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SLOOP ДЛИНЫ ЗАПИСИ 90 $£ХХТ SIX RRECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 95 MEND RDBUFF F3rBUF/RECL,04,2048 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 40 LDCH s=X'04' УСТАНОВКА СИМВОЛА EOR 42 RMO A,S 47 +LDT 42048 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 $AALOOP TD s=X'F3z ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $AALO0P ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD s=X'F3' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 65 COMPR ArS ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ 3AAEXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH BUFrX ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SAALOOF ДЛИНЫ ЗАПИСИ 90 $aaexit stx 6REQL СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ & которого это значение было получено. Однако использование переменной периода макрогенерации оправданно здесь хотя бы уже потому, что оно указывает на то, что в обоих предложен ниях IF используются одинаковые условия. Кроме того, про- верка значения переменной может оказаться быстрее, чем по- вторение исходного сравнения, особенно если оно связано с вы- числением сложных булевских выражений.
4.2. Машинно-неза&исимые особенности макропроцессора 195 RDBUFF 0Е,BUFFER,LENGTH,,80 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 47 +LDT ♦80 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 $ABLOOP TD =X'0Ez ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $ABLOOP’ ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD *=X'0E' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 75 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SABLOOP ДЛИНЫ ЗАПИСИ 90 ФАВЕХ1Т STX LENGTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ б RDBUFF Fl, BUFF,RLENG , 04 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 40 LDCH s=Xz04z УСТАНОВКА СИМВОЛА EOR 42 RMO A,S 47 +LDT ♦4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 $ACLOOP TD ==XZF1Z ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $>ACLOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD SXZF1Z ЧТЕНИЕ СИМВОЛА В РЕГИСТР. А 65 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ $ACEXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH BUFF,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT tACLOOP ДЛИНЫ ЗАПИСИ 90 tACEXIT STX RLENG СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ r Рис. 4.8. Использование условных предложений периода макрогенерации На рис. 4.86—г изображены макрорасширения, порожденные тремя различными макроинициализациями, иллюстрирующие работу предложений IF во фрагменте на рис. 4.8а. Вы должны внимательно разобраться с этими примерами и убедиться, что понимаете, каким образом было получено данное макрорасши- рение, исходя из текста макроопределения и предложения мак- роинициализации. Реализация только что описанных средств условной макро- генерации достаточно проста. Макропроцессор должен вести таблицу имен переменных периода макрогенерации, содержа- щую их значения. Эта таблица должна пополняться либо мо- дифицироваться при обработке предложений SET. Когда бы ни понадобилось текущее значение переменной периода макроге- нерации, оно берется из этой таблицы. Когда в процессе макрогенерации встречается предложение IF, вычисляется соответствующее булевское выражение. Если оно истинно, то макропроцессор продолжает обрабатывать стро- 7*
196 Гл. 4. Макропроцессоры ки из таблицы 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.96 параметру EOR соответствует список (00, 03, 04). Любой из этих кодов явля- ется признаком конца записи. Для упрощения макроопределе- ния параметр &MAXLTH удален; максимальная длина записи всегда полагается равной 4096. В макроопределении на рис. 4.9а используется циклическая конструкция WHILE периода макрогенерации. Предложение WHILE указывает на то, что последующие строки тела макро- определения до ближайшего предложения ENDW должны мно- гократно переноситься в результирующую программу до тех
4.2. Машинно-независимые особенности макропроцессора 197 25 RDBUFF MACRO NDEV,&BUFADR , &RECLTH,&EOR 27 &EORCT SET XNITEMS(^EOR) 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 45 ♦LDT ♦4096 МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ = 4096 50 SLOOP TD *=X'&INDEV' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $LOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD -X'&INDEV' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 63 iCTf? SET 1 64 WHILE UCTR LE &EORCT) 65 COMF s=X'0000&EORC&CTR3' 70 JEQ $EXIT 71 &CTR SET &CTR+1 73 ENDW 75 STCH &BUFADR,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR Т ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT $LOOP ДЛИНЫ ЗАПИСИ 90 $EXIT STX 6RECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 95 MEND RDBUFF F2,BUFFER r LENGTH Л 00 r03 , 04 > 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 47 ♦LDT ♦4096 МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ * 4096 50 &AALOOP TD =X'F2' ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ $AALOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD =X'F2' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 65 COMP -X'000000z 70 JEQ $AAEXIT 65 COMP -X'000003' 70 JEQ $AAEXIT 65 COMP ~X'000004' 70 JEQ Saaexit 75 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR I ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT $AALOOP ДЛИНЫ ЗАПИСИ 90 $AAEXIT STX LENGTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 6 Рис. 4.9. Использование предложений условного перехода периода макро- генерации. пор, пока некоторое условие является истинным. Как и рань- ше, проверка этого условия и зацикливание осуществляются в период макрогенерации. В проверяемых условиях могут исполь- зоваться переменные периода макрогенерации и аргументы, но не могут быть использованы значения, вычисляемые в период выполнения программы. Примером использования конструкции WHILE—ENDW яв- ляются строки 63—73 на рис. 4.9а. Переменной периода макро- генерации &EORCT предварительно (строка 27) было присвое-,
198 Гл. 4. Макропроцессоры но значение %NITEMS(&EOR). %NITEMS — это микропроцес- сорная функция, значением которой является количество эле- ментов в списке, задаваемом в качестве аргумента этой функ- ции. Например, если значение аргумента, соответствующее па- раметру EOR, есть (00, 03, 04), то значение ,0/oNITEMS(&EOR) будет равно 3. Переменная периода макрогенерации &CTR используется для подсчета того, сколько раз сгенерированы (перенесены из тела макроопределения в результирующую программу) строки, следующие за предложением WHILE. Начальное значение &CTR полагается равным 1 (строка 63) и далее увеличивается на 1 при каждом проходе по циклу (строка 71). Само предложение WHILE говорит о необходимости повтора цикла периода мак- рогенерации до тех пор, пока значение переменной &CTR мень- ше или равно значению переменной &EORCT. Это означает, что строки 65—70 будут сгенерированы для каждого элемента списка. Переменная &CTR используется в качестве индекса для выборки соответствующего элемента списка. На первой итерации выражение &EOR[&CTR] в строке 65 будет иметь значение 00, на второй итерации — значение 03 и т. д. На рис. 4.96 изображено расширение предложения макро- инициализации с использованием макроопределения на рис. 4.9а. Вы должны тщательно проанализировать этот пример для того, чтобы убедиться, что вы правильно понимаете смысл предложения WHILE. Реализация цикла WHILE периода макрогенерации также достаточно проста. Когда макропроцессор встречает предложе- ние WHILE, он вычисляет соответствующее булевское выраже- ние. Если оно ложно, то макропроцессор пропускает строки в таблице DEFTAB до предложения ENDW и продолжает далее работать, как обычно, в режиме безусловной макрогенерации. Если же это выражение истинно, то макропроцессор продолжа- ет обрабатывать строки таблицы DEFTAB обычным образом до предложения ENDW. По предложению ENDW макропроцессор возвращается к предложению WHILE, перевычисляет соот- ветствующее булевское выражение и действует далее в соответ- ствии с этим новым вычисленным значением так, как уже опи- сано. Предложенный метод реализации не допускает вложенных конструкций WHILE. Вы можете подумать над тем, каким ал- горитмом могут поддерживаться такие вложенные конструкции (см. упр. 4.2.12). 4.2.4. Ключевые макропараметры Все предложения макроинициализации, с которыми мы име- ли дело до сих пор, имели позиционные параметры, т. е. соот- ветствие формальных параметров и аргументов осуществлялось
4.2. Машинно-независимые особенности макропроцессора 199 посредством соотнесения их позиций в макропрототипе и в предложении макроинициализации. Используя позиционные па- раметры, программист должен очень внимательно следить за правильным порядком следования аргументов. Если некоторый аргумент должен быть опущен, то предложение макроинициа- лизации должно содержать пустой аргумент (две подряд иду- щие запятые) для сохранения соответствия между аргументами и параметрами. (См., например, предложение макроинициали- зации на рис. 4.8в.) Позиционные параметры вполне приемлемы для большин- ства макроинструкций. Однако если макрос имеет большое число параметров, а в типичном предложении макроинициали- зации используются только некоторые из них, то для определе- ния этих параметров может быть более удобной другая, непо- зиционная форма записи. Такого рода макросы могут возникать в ситуациях, когда результатом расширения одного предложе- ния макроинициализации является большая и сложная после- довательность предложений (может быть, даже вся операцион- ная система). В подобных случаях большинству параметров можно присвоить приемлемые значения по умолчанию, а в предложении макроинициализации задать только те из них, значения которых отличаются от принятых по умолчанию. Предположим, например, что некоторая макроинструкция GENER имеет 10 возможных параметров, но при ее инициали- зации должны быть заданы только третий и девятый парамет- ры. Соответствующее предложение макроинициализации будет выглядеть следующим образом: GENER ,,DIRECT„„„3. При использовании параметров другого типа, называемых клю- чевыми, значение каждого аргумента записывается вместе с ключевым словом, которое является именем соответствующего параметра. Аргументы в этом случае могут записываться в лю- бом порядке. Если предположить, что третий параметр в пре- дыдущем примере имеет имя &TYPE, а девятый — имя &CHANNEL, то предложение макроинициализации будет вы- глядеть так: GENER TYPE = DIRECT,CHANNEL = 3 Это предложение гораздо легче читается и содержит потенци- ально меньше ошибок, чем его позиционный вариант. На рис. 4.10а изображен вариант макроса RDBUFF с ис- пользованием ключевых параметров. За исключением способа записи, все параметры те же, что и на рис. 4.8а. В макропро- тотипе после каждого имени параметра следует знак равенства, являющийся признаком ключевого параметра. После знака равен- ства идет значение соответствующего параметра, принимаемое.
25 RWFF MACRO iBUFAW?», MW TH®, iSJR=04, ШХ1Л№= 26 IF UEOR NE ' ) 27 «.EORCK SET 1 28 ENDIF 30 CLEAR X ОЧИСТКА .СЧЕТЧИКА ЦИКЛА 35 CLEAR A 38 IF <&EORCK EQ 1) 40 LDCH -X'&EOR' УСТАНОВКА СИМВОЛА EOR 42 RMO A,S 43 ENDIF 47 +LDT ♦&MAXLTH УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 JLOOP TD =X'&INDEVZ ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ SLOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD -X'&INDEU' ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 63 IF (&EORCK EQ 1) 65 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ $EXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 73 ENDIF 75 STCH &BUFADR,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT Sloop ДЛИНЫ ЗАПИСИ 90 $EXIT STX 6RECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 95 1 t MEND RDBUFF BUFADR=BUFFER,RECLTH=LENGTH 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 40 LDCH =X'04Z УСТАНОВКА СИМВОЛА EOR 42 RMO A,S 47 +LDT *4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 SAALOOP TD «=X,F1Z ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ SAALOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 60 RD -ХТ1/ ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 65 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ 70 JEQ SAAEXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SAALOOP ДЛИНЫ ЗАПИСИ 90 SAAEXIT STX LENGTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ RDBUFF RECLTH=LENGTH, EUFADR=BUFFER, EOR=, INI'EV=F3 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 47 +LDT *4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ S® UBLOOP TD t=XzF3z ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 55 JEQ SABLOOP ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ RD s=X'F3z ЧТЕНИЕ СИМВОЛА В РЕГИСТР А 75 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SABLOOP ДЛИНЫ ЗАПИСИ 90 Sabexit STX LENGTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ
4.3. Варианты построения макропроцессоров 201 по умолчанию. Считается, что параметр принимает это значение, если его имя не встретилось в списке аргументов предложения макроинициализации. В нашем примере значение параметра &INDEV принимается равным F1, а для параметра &BUFADR значение по умолчанию не определено. Определение значений некоторых параметров по умолча- нию во многих случаях упрощает макроопределение. Например, оба макроопределения на рис. 4.10а и рис. 4.8а предусматрива- ют установку максимальной длины записи равной 4096, если пользователь не задал другого значения. Если задано значение по умолчанию, как на рис. 4.10а, то эти действия выполняются автоматически. Для выполнения этих же действий в макро- определении на рис. 4.8а понадобилась конструкция IF— —ELSE—ENDIF. Другие фрагменты рис. 4.10 содержат примеры макрорас- ширений предложения макроинициализации с ключевыми пара- метрами. На рис. 4.106 все параметры имеют значения, задан- ные по умолчанию. На рис. 4.10в формальному ключевому параметру &INDEV соответствует значение F3, а значением па- раметра &EOR задана пустая строка. Значения, заданные для этих параметров по умолчанию, игнорируются. Обратите вни- мание, что аргументы в предложении макроинициализации мо- гут быть расположены в любом порядке. Имеет смысл внима- тельно проследить на этих примерах использование заданных по умолчанию параметров. 4.3. Варианты построения макропроцессоров В этом разделе мы обсудим некоторые базовые варианты построения макропроцессоров. Представленный на рис. 4.5 алгоритм не сработает, если внутри тела макроопределения встретится предложение макроинициализации. Часто, однако, желательно использовать макросы именно таким образом. В разд. 4.3.1 изучается эта проблема и предлагаются некото- рые пути ее решения. Хотя подавляющее большинство макросредств используется в связи с программированием на языке ассемблера, имеются также и другие приложения. В разд. 4.3.2 обсуждается макро- процессор общего назначёния, не связанный ни с каким кон- кретным языком программирования. В разд. 4.4 приведен при- мер подобного макропроцессора. В разд. 4.3.3 обсуждается дру- гая сторона того же вопроса: интеграция макропроцессора с конкретным ассемблером или компилятором. Мы обсудим Рис. 4.10. Использование ключевых параметров.
10 RDBUFF MACRO &BUFADRr&RECLTHriINDEV 15 20 . МАКРОС ДЛЯ ЧТЕНИЯ ЗАПИСИ В БУФЕР 25 30 CLEAR X ОЧИСТКА СЧЕТЧИКА ЦИКЛА 35 CLEAR A 40 CLEAR S 45 +LDT ♦4096 УСТАНОВКА МАКСИМАЛЬНОЙ ДЛИНЫ ЗАПИСИ 50 3'' ПОР RDCHAR &INDEV ПРОВЕРКА ГОТОВНОСТИ УСТРОЙСТВА 65 COMPR A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ . 70 JEQ $EXIT ВЫХОД ИЗ ЦИКЛА ЕСЛИ КОНЕЦ ЗАПИСИ 75 STCH &BUFADR,X ЗАПИСЬ СИМВОЛА В БУФЕР 80 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ 85 JLT SLOOP ДЛИНЫ ЗАПИСИ 90 SEXI Г STX &RECLTH СОХРАНЕНИЕ ДЛИНЫ ЗАПИСИ 95 MEMIT -5 RDCHAR MACRO &IH 10 15 , МАКРОС ДЛЯ ЧТЕНИЯ СИМВОЛА В РЕГИСТР А 20 25 TD t=X4lN' ПРОВЕРКА ВХОДНОГО УСТРОЙСТВА 30 JEQ *-3 ЦИКЛ ОЖИДАНИЯ ГОТОВНОСТИ 35 RD =X'6IN' ЧТЕНИЕ СИМВОЛА 40 MENU б RDBUFF BUFFERrLENGTHгFl в ARGTABг Параметр Значение 1 BUFFER 2 LENGTH 3 Fl 4 (не используется) г ARGTABs Параметр Значение । 1 • 1 1 F1 (не используется) А
4.3. Варианты построения макропроцессоров 203 возможности кооперации между макропроцессором и трансля- тором и укажем на потенциальные преимущества и проблемы, которые возникают при такой интеграции. 4.3.1. Рекурсивная макрогенерация На рис. 4.3 был представлен пример определения одной макроинструкции внутри другой. Мы, однако, не встречались с тем, чтобы внутри тела макроопределения были предложения макроинициализации. На рис. 4.11 представлен пример подоб- ного использования макросов. Макроопределение RDBUFF на рис. 4.11а в основном то же, что и на рис. 4.1. Для наглядности изменен только порядок параметров. В этом примере мы предполагаем, что макроопределение RDCHAR уже описано. Его функцией является чтение одного символа с заданного устрой- ства в регистр А с учетом необходимых проверок готовности устройства. Соответствующее макроопределение изображено на рис. 4.116. Использование макроса, подобного 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. Таким образом, макропроцессор «забу- дет», что он находился в середине процесса макрорасширения, Рис. 4.11. Пример вложенных макроинициализаций,
204 Гл. 4. Макропроцессоры когда встретил предложение инициализации макроса RDCHAR. Кроме того, аргументы первой макроинициализации (RDBUFF) оказались потерянными, поскольку их значения в таблице ARGTAB затерлись новыми аргументами макроса RDCHAR. Основной причиной этих трудностей является рекурсивный вызов процедуры EXPAND. Сначала она будет вызвана при об- работке предложения макроинициализации макроса RDBUFF. Далее она вызывает процедуру PROCESSLINE (строка 50), которая делает еще одно обращение к EXPAND до возврата из первого вызова этой процедуры. Те же трудности возникнут и с процедурой PROCESSLINE, поскольку она также вызыва- ется рекурсивно. Например, возникает вопрос, куда передавать управление после конца работы процедуры PROCESSLINE: в основной цикл работы макропроцессора или же в цикл внутри процедуры EXPAND. Эти трудности легко разрешимы, если макропроцессор пи- шется на языке программирования, допускающем рекурсивные вызовы (таком как Паскаль или ПЛ/1). Сам компилятор по- заботится о том, чтобы значения всех переменных, определен- ных внутри процедуры, были сохранены при рекурсивном об- ращении. Компилятор позаботится также и о других деталях обработки рекурсивных вызовов, включая определение адреса возврата из процедуры. (В гл. 5 мы детально обсудим, как компилятор обрабатывает подобные рекурсивные вызовы.) Если же допускающий рекурсию язык программирования недоступен, то программист обязан сам позаботиться о таких вещах, как адрес возврата и значения локальных переменных. В этом слу- чае PROCESSLINE и EXPAND, может быть, вообще не будут оформлены как процедуры. Вместо этого та же логика работы может быть реализована с помощью операторов цикла с хра- нением значений переменных в стеке. Используемые здесь ме- тоды реализации описаны в гл. 5 при обсуждении рекурсии. Пример подобной реализации содержится в работе Донован [1972]. 4.3.2. Макропроцессоры общего назначения Наиболее часто макропроцессоры используются при програм- мировании на языке ассемблера. Часто такие макропроцессоры встроены в ассемблер. Специализированные макропроцессоры были созданы также для некоторых языков программирования высокого уровня. (См., например, Керниган и Плаугер [1976].) Эти специализированные макропроцессоры во многом аналогич- ны с точки зрения выполняемых функций и используемого под- хода. Их различия связаны в основном с особенностями кон- кретного языка программирования. В этом разделе мы обсу-
4.3. Варианты построения макропроцессоров 205 дим макропроцессоры общего назначения. Они не зависят ни от какого конкретного языка программирования и могут быть использованы с целым рядом различных языков. Преимущества такого общего подхода к построению макропроцессоров очевид- ны. Программист не должен изучать специализированные мак- росредства для каждого компилятора или языка ассемблера. В результате экономится много времени и средств при обуче- нии. Стоимость разработки макропроцессора общего назначения несколько больше по сравнению со специализированными мак- ропроцессорами. Однако эти затраты не будут повторяться для каждого языка. Результатом будет существенное сокращение общих затрат на разработку и сопровождение математического обеспечения. На протяжении ряда лет экономия на сопровож- дении может превысить первоначальные затраты на разработ- ку математического обеспечения. Несмотря на все эти преимущества, в настоящее время име- ется сравнительно мало макропроцессоров общего назначения. Одна из причин подобного положения дел связана с огромным количеством деталей, которые необходимо иметь в виду при программировании на любом реально используемом языке. Эти многочисленные детали могут быть встроены внутрь специали-. зированного макропроцессора. Макропроцессор же общего на- значения должен предоставлять пользователю средства, позво< ляющие ему самому определить необходимый набор правил, которому должен следовать макропроцессор. При работе с обычными языками программирования встречается несколько ситуаций, в которых обычная подстанов- ; ка макропараметров не должна осуществляться. Например, ком-: ментарии, как правило, игнорируются макропроцессором (по крайней мере при обработке списка параметров). Каждый язык; программирования имеет свои собственные средства для идеи-; тификации комментариев. Они могут идентифицироваться с по-; мощью ключевых слов (как в Алголе) или же с помощью спе-; циальных символов в начале и в конце комментария (как в' Паскале). Некоторые языки, например Фортран, используют! специальный символ, означающий, что вся строка является ком-: ментарием. В большинстве языков ассемблера любые символы; в строке, следующие за полем операнда, автоматически рас-! сматриваются как комментарии. В некоторых языках исполь-; зуется специальный символ разделитель. Все символы, находя-' щиеся после него в строке, рассматриваются как комментарии. Другие различия между языками программирования связаны со средствами группирования отдельных элементов языка, вы- ражений или предложений. Большинство языков использует для этого скобки. В некоторых случаях макропроцессору обще- го назначения будет необходимо знать средства подобного группирования при сканировании исходных предложений^
206 Гл. 4. Макропроцессоры Однако некоторые языки используют другие символы вместо круглых скобок (например, символы [и]). Некоторые языки ис- пользуют для этих целей ключевые слова begin и end. Более общая проблема состоит в структуре лексем языка программирования, например идентификаторов, констант, опе- раторов, ключевых слов. Языки существенно различаются по ограничениям, которые они накладывают на длину идентифика- торов и правила написания.констант. В некоторых случаях пра- вила построения подобных лексем различны в разных частях программы (например, в предложении FORMAT языка Фор- тран или в предложении DATA DIVISION в языке Кобол). В некоторых языках имеются операторы, записываемые не- сколькими символами, такие как ** в языке Фортран и :== в Паскале. Если макропроцессор будет воспринимать их как два независимых символа, а не как один оператор, могут встретить- ся определенные трудности. Даже формат записи предложений исходной программы во входном файле может привести к определенным трудностям. Макропроцессор должен учитывать, являются ли пробелы разделителями или должны полностью игнорироваться, способы записи одного предложения в несколь- ких строках, специальные соглашения по форматированию пред- ложений, подобные принятым в языках Фортран и Кобол. Другая проблема, которая может возникнуть при разработ- ке макропроцессора общего назначения, связана с синтаксисом предложений макроопределения и макроинициализации. Для большинства специализированных макропроцессоров предложе- ния макроинициализации весьма похожи на другие предложе- ния базового языка программирования. (Например, инициали- зация макроса RDBUFF на рис. 4.1 имеет ту же форму, что и предложение на языке ассемблера УУМ.) Эта схожесть форм имеет целью облегчить написание и чтение программ. Этого трудно достичь для макропроцессора общего назначения, кото- рый предназначен для работы с языками программирования, имеющими различный формат предложений. В разд. 4.4.3 мы кратко опишем макропроцессор общего на- значения, в котором решены многие из перечисленных выше вопросов. Обсуждение макропроцессоров общего назначения и макропроцессоров для языков программирования высокого уров- ня содержится в работах Коул [1981], Браун [1974] и Кэмп- бел-Келли [1973]. 4.3.3. Макропроцессоры, встроенные в трансляторы Все обсуждавшиеся до сих пор макропроцессоры могут быть названы препроцессорами. Они обрабатывают макроопределе- ния и предложения макроинициализации, генерируя расширен-
4.3. Варианты построения макропроцессоров 207 ную версию исходной программы. Эта расширенная программа далее используется в качестве входной для ассемблера или компилятора. В этом разделе мы обсудим другую альтернати- ву— реализацию функций макропроцессора самим трансля- тором. Простейший способ достижения подобного объединения со- стоит в использовании принципа «строка за строкой». В этом случае макропроцессор читает предложение исходной про- граммы и выполняет все описанные выше функции. При этом строки, сгенерированные макропроцессором, передаются на вход транслятора по мере того, как они генерируются (последова- тельно одна за одной), вместо того, чтобы писать расширенную программу в файл. В этом случае макропроцессор выполняет функции, аналогичные подпрограмме чтения очередной строки для ассемблера или компилятора. Подобный подход имеет несколько преимуществ. Он позво- ляет избежать дополнительного прохода по программе (записи ее в промежуточный файл и последующего чтения) и в резуль- тате может быть более эффективным, чем использование мак- ропроцессора. Некоторые таблицы, требующиеся и макропроцес- сору, и транслятору, могут быть объединены. Например, таблица ОВТАВ в ассемблере и таблица NAMTAB в макропроцес- соре могут быть реализованы в виде одной таблицы. Кроме того, многие вспомогательные программы и функции могут ис- пользоваться как транслятором, так и макропроцессором. Сюда относятся операции сканирования входной строки, поиска по таблицам, преобразование числовых констант из внешнего во внутреннее представление. В рамках этого подхода облегчается привязка диагностических сообщений к предложению исходной программы, вызвавшей ошибку (т. е. к соответствующему пред- ложению макроинициализации). При использовании макропре- процессора подобная ошибка может быть отнесена только к соответствующему предложению макрорасширения. Програм- мист будет в этом случае самостоятельно искать исходную при- чину ошибки. Хотя макропроцессор, реализующий принцип «строка за строкой», и может использовать те же подпрограммы, что и транслятор, по-прежнему его функции и функции транслятора остаются существенно различными. Основной формой связи ме- жду ними является передача предложений программы с выхода одного на вход другого. Однако можно себе представить еще более тесное взаимодействие между макропроцессором и ас- семблером. Речь идет о макропроцессоре, встроенном в транс- лятор. Встроенный макропроцессор потенциально может использо- вать любую информацию об исходной программе, имеющуюся у транслятора. Объем этой информации существенно различается
2Э8 Гл. 4. Макропроцессоры в разных системах. При относительно простом варианте взаимодействия макропроцессор использует такие операции транслятора, как сканирование, обработка констант и т. д. Эти операции в любом случае должны осуществляться компилято- ром или ассемблером; макропроцессор просто использует их результаты, не вдаваясь в такие детали, как изображение од- ной операции языка несколькими символами, расположение од- ного предложения языка на нескольких строках, формат лек- сем и т. п. Это особенно важно в тех случаях, когда эти пра- вила различаются в разных частях программы (например, внутри операторов FORMAT и символьных констант языка Фортран). Сканирование лексем, о котором только что шла речь, кон- цептуально очень просто. Однако многие существующие языки программирования имеют особенности, доставляющие опреде- ленные трудности и на этом этапе. Классическим примером яв- ляется предложение Фортрана DO 100 1 = 1, 20 Это оператор цикла DO, где DO распознано как ключевое сло- во, 100 — метка предложения, I — имя переменной и т. д. По- скольку пробелы никак не учитываются в предложениях Фор- трана (кроме символьных констант), похожее предложение DO 100 1 = 1 имеет совершенно другой смысл. Это оператор присваивания пе- ременной DO 1001 значения 1. Таким образом, правильная ин- терпретация термов DO, 100, I не может быть осуществлена до тех пор, пока не проведен анализ всего предложения. Такая интерпретация будет очень важна, например, в случае, если макропроцессор должен заменить переменную с именем I. Ком- пилятор с Фортрана обязан разбираться с подобными ситуация- ми. Однако для обычного макропроцессора (не встроенного в компилятор) это сделать очень трудно. Такой макропроцессор может иметь дело с предложениями исходной программы только как со строками символов, не имея возможности выделить их отдельные элементы. При более тесной связи с транслятором встроенный макро- процессор может обрабатывать макроинструкции, смысл кото- рых зависит от того контекста, в котором они оказались. На- пример, с помощью такого макроса можно задать замену имен только переменных или констант определенного типа либо только переменных, являющихся переменными цикла в пред- ложениях DO. Процесс макрогенерации может также зависеть от множества характеристик аргументов предложения макро- инициализации. (Примером может служить описание макропро- цессора System/370 в разд. 4.5.1.)
4.4. Примеры реализации 209. Макропроцессоры, тесно связанные с транслятором, име- ют, конечно, и свои недостатки. Они должны быть спроектиро- ваны специально для работы с конкретной реализацией некото- рого ассемблера или компилятора (а не просто для работы с языком программирования). Стоимость разработки такого мак- ропроцессора должна быть прибавлена к стоимости разработки транслятора, что приводит к более дорогим компонентам мате- матического обеспечения. Кроме того, ассемблер или компиля-. тор становится больше и сложнее, чем это было бы в случае использования макропрепроцессора. Размер транслятора может представлять собой проблему, если транслятор предназначен для работы на ЭВМ с ограниченной оперативной памятью. В любом случае увеличиваются накладные расходы на тран- сляцию. (Некоторые ассемблеры со встроенными макропроцес- сорами требуют больше времени на обработку одной строки ис- ходной программы, чем некоторые компиляторы на той же са- мой ЭВМ.) Решение вопроса о типе макропроцессора, который, должен использоваться, опирается на данные о предполагае- мой частоте и сложности макрогенерации и другие характери- стики операционного окружения. 4.4. Примеры реализации В этом разделе мы кратко опишем три реальных макропро- цессора. Как и раньше, не будем пытаться охватить все харак- теристики каждой системы, а сосредоточимся на наиболее ин- тересных особенностях. Первые два примера посвящены макро- процессорам, тесно связанным с ассемблером (для System/370 и ЭВМ серии VAX). Третий пример — макропроцессор общего назначения, предназначенный для использования в качестве препроцессора с самыми различными языками программиро- вания. 4.4.1. Макропроцессор System/370 Рассматриваемый в этом разделе макропроцессор Sy- stem/370 тесно связан с ассемблером этой системы. Он обес- печивает все функции макропроцессора, которые мы обсужда- ли, включая обработку макроопределений и макроинициализа- ций внутри тела макроопределения. В макроинструкциях могут использоваться позиционные или ключевые параметры либо смесь позиционных и ключевых параметров. Управляющие пред- ложения позволяют пользователю задавать различные режимы работы макропроцессора, например разрешить или запретить появление предложений текста макрорасширения в листинге исходной программы. Комментарии в теле макроопределений
210 Гл. 4. Макропроцессоры могут игнорироваться, а могут и остаться в листинге ассембле- ра в зависимости от способа их записи. Существенное отличие макропроцессора System/370 от рас- смотренного нами макропроцессора для УУМ состоит в струк- туре предложений условной макрогенерации. В макроязыке для System/370 они называются предложениями условного ассембли- рования. Хотя эти предложения и предназначены в основном для макрогенерации, они могут появиться также и вне макро- определений. Язык ассемблера System/370, дополненный опера- торами условного ассемблирования, допускает использование переменных периода ассемблирования, которые аналогичны об- суждавшимся выше переменным периода макрогенерации. Этим переменным могут быть присвоены арифметические, двоичные или символьные значения. Этим трем типам значений соответ- ствуют три типа таких переменных. Переменные периода ассем- блирования могут быть как локальными, так и глобальными. На локальные переменные можно ссылаться только внутри того макроопределения или такой подпрограммы на ассемблере, ко- торые содержат их определения. Если локальные переменные, имеющие одно имя, будут определены в нескольких макроопре- делениях, то транслятор будет рассматривать их как различные переменные. Глобальные переменные могут использоваться в любом месте программы. Так, например, глобальной перемен- ной может быть присвоено значение в момент обработки одного макроса, а использоваться это значение может в другом мак- росе. Предложения условного ассемблирования, управляющие по- рождением строк результирующей программы, существенно от- личны от условных предложений периода макрогенерации для УУМ. Базовой управляющей конструкцией является оператор перехода периода ассемблирования. При выполнении такого перехода ассемблер (или макропроцессор) прерывает последо- вательную обработку предложений программы и в качестве очередного предложения берет то, которое указано в операторе перехода. Эти переходы могут быть как «вниз», так и «вверх» по входному потоку. На рис. 4.12 изображены примеры макроопределения и тек- ста макрорасширения с использованием макроязыка Sy- stem/370. Это макроопределение предназначено для генерации кода, осуществляющего сложение двух элементов данных и за- поминание результата. В зависимости от типа аргументов — целые или с плавающей точкой — порождается различный код. Если аргументы имеют какой-либо другой или не совпадающий между собой тип, то генерируется сообщение об ошибке. Фор- ма макроопределения на рис. 4.12а в основном та же, что и в наших предыдущих примерах. Макроопределение начинается предложением MACRO и заканчивается предложением MEND.
4.4. Примеры реализации 211 1 MACRO 2 &NAME ADD &0Pl,&0P2,&SUM 3 LCLC &TYPE 4 AIF (T'&OPl NE T'&OP2) .MIXTYP 5 AIF (T'&OPl EQ 'F') .INTGR 6 AIF (T'&OPl EQ 'E') .FLOAT 7 AGO .TYPERR 8 .FLOAT ANOP 9 &TYPE SETC 'E' £0 .INTGR ANOP 11 &NAME L&TYPE 2,&OP1 12 A&TYPE 2,&0P2 13 ST&TYPE 2,&SUM 14 MEXIT 15 .MYXTIP MNOTE 'СМЕШАННЫЕ ТИПЫ ОПЕРАНДОВ' 16 MEXIT 17 •TYPERR MNOTE 'НЕДОПУСТИМЫЙ ТИП ОПЕРАНДОВ 18 MEND LAB ADD I, J,К I LAB L 2,1 A 2,J ST 2,К & ADD X,Y,Z l LE 2,X AE 2,Y STE 2,Z ADD I,Y,Z 1 **** СМЕШАННОЕТИПЫ ОПЕРАНДОВ г Рис. 4.12. Примеры макроопределения и макрорасширений для System/370. В строке 15 рис. 4.12 имя метки должно быть MIXTYP. — Прим, перев.
212 Гл. 4. Макропроцессоры Макропрототип расположен сразу же за предложением MACRO (строка 2). Все макропараметры и переменные периода ассемб- лирования начинаются символом &. В этом примере исполь- зуются только позиционные параметры. Предложение LCLC в строке 3 определяет переменную &TIPE строкового типа. Эта переменная определена как ло- кальная в том макроопределении, в котором она описана. По умолчанию ей присваивается начальное значение — пустая строка. Предложение в строке 4 (AIF) является предложением условного перехода периода ассемблирования. Ассемблер вы- числяет булевское выражение в скобках и переходит на обра- ботку другого места исходной программы, если это выражение истинно. В противном случае ассемблер продолжает обрабаты- вать следующую строку исходной программы. В нашем примере адрес перехода задается с помощью специальной метки MIXTYP, которая описана в строке 15. Аргументами булевского выражения в предложении AIF являются типы параметров макроопределения. Тип параметра &ОР1 (обозначаемый T'&OPl) есть символ F, если соответствующий параметр явля- ется целой переменной длиной в одно слово, и символ Е, если он является числом с плавающей точкой. Таким образом, пред- ложение AIF в строке 4 определяет переход к строке 15, если типы двух параметров различны. Если же их типы совпадают, то ассемблер продолжает работу со строки 5. Другое пред- ложение AIF имеет аналогичную структуру. Предложение AGO в строке 7 является оператором безус- ловного перехода периода ассемблирования, предложение ANOP (строки 8 и 10) — пустым оператором. Эти предложения используются для описания меток FLOAT и INTGR. В строках 14—17 содержатся макродирективы двух новых типов. Предло- жение MNOTE вызывает печать сообщения об ошибке в лис- тинге ассемблера. Реакция ассемблера на это сообщение будет такой же, как реакция на собственную ошибку. Предложение MEXIT предписывает макропроцессору закончить процесс по- рождения текста макрорасширения текущего предложения мак- роинициализации, несмотря на то что предложение MEND еще не встретилось. На рис. 4.126—г изображены предложения макроинициали- зации и соответствующие им макрорасширения. Предполагает- ся, что I, J и К являются целыми переменными длиной в одно слово, а X, Y, Z — переменными с плавающей точкой. Чтобы убедиться, что вы правильно понимаете работу предложений AIF и AGO, вам необходимо проследить по шагам весь про- цесс макрогенерации, используя макроопределение на рис. 4.12а. В рассмотренном примере предложения AIF и AGO обеспе- чивают действия, эквивалентные конструкции IF—ELSE— ENDIF. Поскольку цикл WHILE также может быть запрограм-
4.4. Примеры реализации 213 мировая с использованием предложений 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. Принципы записи макро-
214 . Гл. 4. Макропроцессоры определений и предложений макроинициализаций те же, что и для УУМ, но есть и интересные отличия. Имена параметров макроинструкций не обязаны начинаться с & или какого-либо другого специального символа. В резуль- тате процесс сканирования для обнаружения параметров услож- няется. Кроме того, чаще должен использоваться оператор кон- катенации. Рассмотрим в качестве примера макроопределение на рис. 4.13а. В предложении 3 TSTL R'NUM операндом является символ R, конкатенированный со значением параметра NUM. Для обозначения операции конкатенации в макроязыке системы VAX используется апостроф '. Если бы этот операнд был записан просто как RNUM, то последователь- ность символов NUM не была бы распознана в качестве мак- ропараметра. Таким образом, оператор конкатенации тут совер- шенно необходим. Сравните эту ситуацию с примером на рис. 4.12. В строке 13 этого примера не требуется никакого опе- ратора конкатенации при записи терма ST&TYPE, поскольку амперсанд является признаком того, что &TYPE является па- раметром. Вообще говоря, макропроцессор мог бы распознать последовательность символов NUM на рис. 4.12 как имя пара- метра без оператора конкатенации. Однако в этом случае мак- ропроцессор должен был бы иметь средства, позволяющие за- претить подстановку параметров (например, мы можем не за- хотеть, чтобы NUMBER было превращено в 5BER). Если же макропараметр не сконкатенирован ни с какими другими сим- волами, то апостроф, конечно же, не нужен. Макропроцессор системы VAX также предоставляет средства генерации уникальных локальных меток при макрогенерации. Программист описывает локальную метку, путем включения ее в список параметров макроопределения, добавив предварительно перед именем метки символ ?. Задав соответствующий аргу- мент в предложении макроинициализации, можно дать этой метке произвольное значение. Если же оно не определено, то ассемблер сгенерирует новую локальную метку. Метки, гене- рируемые ассемблером, находятся в диапазоне 30000 $— 65535 $ Этот процесс проиллюстрирован макроопределением на рис. 4.13а, предложениями макроинициализации и соответству- ющими макрорасширениями на рис. 4.136—в. В первом макро- расширении метка L1 заменена на 30000$; во втором — на 30001$. Программист может также определить локальные метки вне тела макроопределения (в предложении макроинициализа- ции). Однако, чтобы не было пересечений с метками, генери- руемыми макросредствами, документация ассемблера требует, чтобы метки пользователя не попадали в интервал 30000$ — 65535$, Вы можете сами проследить процесс получения макро-
4.4. Примеры реализации 215 1 2 3 4 6 •MACRO SUB'SIZE'3 TSTL FGEQ MNEGL •ENDM ABSDIF OP1,OP2zSIZE.NUM/?L1 OP1,OP2,R'NUM Г NUM LI R'NUM,R'NUM ABSDIF ABSDIF X,Y,L,0 4" SUBL3 X,Y,R0 TSTL R0 BGEQ 30000$ MNEGL R0,R0 30000$s & ABSDIF 4 SUBW3 TSTL BGEQ MNEGL 30001$: IrJzW,2 I,JZR2 R2 30001$ R2,R2 e Рис. 4.13. Примеры макроопределения и макрорасширений для ЭВМ се- рии VAX. расширения на рис. 4.13, обращая внимание на использование операторов конкатенации и локальных меток. Дальнейшая информация о макропроцессоре системы VAX содержится в DEC [1972] и DEC [1979]. 4.4.3. Макропроцессор общего назначения РМ В этом разделе мы дадим краткое описание макропроцес- сора общего назначения, имеющего имя PM (Pattern Mat- ching). Более подробное описание этой системы имеется в Сасс [1979]. Макроопределения и предложения макроинициализации мак- ропроцессора РМ существенно отличаются от рассмотренных ранее. Средства записи макрошаблонов (макропрототипов), со- ответствующие по назначению заголовкам макроопределений, имеют множество различных вариантов. Некоторые части шаб- лона могут быть вообще опущены; могут содержать набор аль- тернативных конструкций; могут повторяться столько раз, сколько необходимо. Процесс макрогенерации инициализирует- ся при совпадении макрошаблона с некоторым фрагментом
216 Гл. 4. Макропроцессоры входного текста. Предложение макроинициализации (и даже его отдельные аргументы) может быть записано в нескольких строках исходного файла. Язык записи тел макроопределений похож на Алгол. Макропроцессор допускает определение пользователем ло- кальных и глобальных меток периода макрогенерации. Имеются также системные переменные периода макрогенерации, анало- гичные тем, с которыми мы встречались при анализе макропро- цессора System/370. Одна из таких системных переменных яв- ляется счетчиком количества обработанных предложений мак- роинициализации. Значение этой переменной может быть ис- пользовано для генерации уникальных меток. Допустимы также предложения условной макрогенерации, которые функционально (но не по способу записи) близки к условным предложениям макроязыка УУМ. Важной особенностью макропроцессора РМ является то, что пользователь может определить ряд языково-зависимых конструкций. Как мы уже говорили в разд. 4.4.2, учет спе- цифики конкретного языка может представлять большие труд- ности при реализации макропроцессора общего назначения. Оператор skip этого макропроцессора является обобщением идеи комментария. Он выделяет порции входного текста, которые не обрабатываются или должны быть удалены. При этом пользо- вателю предоставлена возможность определения синтаксиса оператора skip. Возможно также потребовать замену вхожде- ний операторов skip на некоторые заданные строки символов. Оператор сору в РМ является обобщением идеи текстовой константы, т. е. порции информации входного текста, которая должна быть непосредственно скопирована в выходной текст без анализа макропроцессором. Пользователь имеет возмож- ность определить синтаксис оператора сору. Символы или лек- семы, определяющие начало и конец копируемого куска текста, могут быть изменены в выходном файле. В дополнение к опе- раторам skip и сору макропроцессор РМ имеет средства опи- сания количества открывающихся и закрывающихся скобок, лексем, состоящих из нескольких символов, правила записи од- ного предложения на нескольких строках, а также средства об- работки пробелов и признака «конец строки». На рис. 4.14 изображен пример макроопределения и пред- ложения макроинициализации для макропроцессора РМ. Мак- роопределение на рис. 4.14а разработано для использования с языком Фортран. Предложение макроинициализации по форме похоже на алгольное предложение for; выходной текст имеет формат Фортрана. Строка 1 определяет шаблон предложения макроинициализации: ключевое слово for, за которым следует параметр id, за которым следует ключевое слово from и т. д. Предложение, заключенное в скобки, определяет альтернатив-
4.4. Примеры реализации 217 1 вюсго 'for' id 'Ггом' Г ('by' b I /) Но' t 'do' bodysnoneg 'od' 2 begin 3 (Xid = Zf 4 Xsnun IF (Xid .GT. Xt) GO TO X(snum+1) 5 Zbody 4 Zid = Zid + >; 7 if b * ' ' then <<Zb)> else <i> fi; 8 (X/GO TO XsnuM 9 ZCsnum + 1) CONTINUE X/>* 10 snutt := snum + ?; 11 end for I from 0 to n-1 do S := S + A*’I) od 6 1-0 9000 IF (I .GT* N-1) GO 10 9001 S = S + Ad) I - I + 1 GO TO 9000 9001 CONTINUE Рис. 4.14. Примеры макроопределения и макрорасширений для макропро- цессора РМ. ные варианты построения предложения макроинициализации. Выражение в скобках может либо быть пустым (обозначено символом /), либо содержать конструкцию by. Таким образом, в предложении макроинициализации конструкция by может либо содержаться, либо быть опущена. Спецификация noneg, следующая за параметром body, определяет, что пробелы и сим- волы «конец строки» являются значимыми (т. е. не должны игнорироваться) при обработке значений макроаргументов. Тело макроопределения записано в алгольном стиле в стро- ках 2—11. Текст, который должен появиться в макрорасшире- нии, заключен в угловые скобки. В строках 3—6 содержатся четыре строки выходного текста. Внутри выходного текста сим- вол % используется для выделения параметров и имен пере- менных периода макрогенерации. Так, например, строка 3 говорит о том, что в выходной файл должно быть переписано id, за которым следует знак равенства, за которым следует зна- чение параметра f. Далее строка кончается. Конец строки вход- ного текста порождает конец строки и в выходном тексте. Предполагается, что переменной периода макрогенерации snum
218 Гл. 4. Макропроцессоры ранее уже присвоено начальное значение 9000. Эта переменная используется для генерации меток предложений Фортрана. В результате обработки строки 7 в выходном тексте появится либо значение параметра Ь, либо 1 в зависимости от того, был ли задан аргумент, соответствующий параметру Ь. Его значе- ние окажется на той же строке, которая начала образовываться при обработке строки 6 макроопределения. Символы %/, ко- торыми начинается строка 8, означают конец строки. Таким образом, предложение GOTO будет расположено в отдельной строке. Оператор присваивания в строке 10 увеличивает значе- ние переменной snum периода макрогенерации таким образом, что в следующем макрорасширении метки оператора Фортрана не будут совпадать с ранее сгенерированными. На рис. 4.146 изображено предложение, которое будет обра- ботано как предложение макроинициализации только что описанного макроопределения. На рис. 4.14в приведен сгенери- рованный текст. (Детали,определяющие формат выходных фор- трановских строк, опущены.) Предполагается, что было опреде- лено соответствующее макроопределение, заменяющее символы := на символ = в операторе присваивания S:=S + A(I). По- дробно проанализируйте этот пример, чтобы лучше понять про- цессы макроинициализации и макрогенерации, реализованные в макропроцессоре РМ. Упражнения Раздел 4.1 1. Примените приведенный на рис. 4.5 алгоритм для обработки исход- ной программы на рис. 4.1. Результат должен совпасть с изображенным на рис. 4.2. 2. Предложение макроинициализации является частью исходной про- граммы. Во многих случаях программист не интересуется предложениями макрорасширения. Каким образом за счет взаимодействия макропроцессора и ассемблера можно добиться того, чтобы в листинге ассемблерной про- граммы были только предложения макроинициализации и отсутствовали предложения макрорасширения? 3. Предположим, мы хотим видеть макроопределения внутри ассемблер- ного листинга. Каким образом макропроцессор и ассемблер могут это реализовать? 4. В большинстве случаев фрагменты комментариев не должны заме- няться на значения макроаргументов, даже если они и совпадают с именами макропараметров. Каким образом можно предотвратить замену параметров внутри комментариев? 5. На основании чего программист должен принять решение о реали- зации некоторой конкретной логической функции с помощью подпрограммы или макроопределения? 6. Напишите алгоритм работы двухпросмотрового макропроцессора, ко- торый при первом просмотре обрабатывает все макроопределения, а на вто- ром — предложения макроинициализации, Можно считать, что вложенные
Упражнения 219 друг в друга макроопределения запрещены и внутри макроопределений не могут встречаться предложения макроинициализации. 7. Модифицируйте приведенный на рис. 4.5 алгоритм таким образом, чтобы макропроцессор брал не описанное программистом макроопределение из библиотеки. 8. Предложите некоторый способ работы с таблицами DEFTAB и NAMTAB. 9. Предположим, что вхождения макропараметров в таблице DEFTAB не заменены соответствующими позиционными обозначениями ?п. Какие изме- нения это потребует в алгоритме работы макропроцессора на рис. 4.5? Раздел 4.2 1. Макроопределение на рис. 4.1 содержит несколько предложений, в которых макропараметры сконкатенированы с другими символами (напри- мер, строки 50 и 75). Почему в этих предложениях нет необходимости ис- пользовать оператор конкатенации? 2. Модифицируйте приведенный на рис. 4.5 алгоритм так, чтобы он обрабатывал операторы конкатенации. 3. Модифицируйте приведенный на рис. 4.5 алгоритм так, чтобы предо- ставить пользователю средства генерации уникальных меток при макро- генерации. 4. Предположим, что мы хотим использовать метки внутри текста макрорасширения, не требуя, чтобы они имели какую-либо специальную фор- му (например, начинались символом %). Каждая такая метка предполагается описанной только внутри соответствующего макрорасширения, что исклю- чает проблему дважды определенных меток. Каким образом макропроцес- сор и ассемблер должны взаимодействовать для обеспечения такой воз- можности? 5. В чем заключается наиболее существенное различие между следую- щими фрагментами: a) LDA ALPHA COMP # 0 JEQ SKIP LDA #3 STA BETA SKIP 6) IF (&ALPHA NE 0) &BETA SET 3 ENDIF 6. Получите макрорасширения следующих двух предложений макроини- циализации, используя макроопределения, приведенные на рис. 4.8а a) RDBUFF F1,BUFFER,LENGTH;00,1024 б) LOOP RDBUFF F2, BUFFER, LTH 7. Модифицируйте приведенный на рис. 4.5 алгоритм так, чтобы допу. гыгн использование операторов присваивания и конструкций IF—ELSE—> ENDIF периода макрогенерации. Можно считать, что вложенные конструк- ции IF отсутствуют. 8. Дополните свой ответ к упр, 7 так, чтобы допустить наличие вло- женных предложений IFS
220 Гл. 4. Макропроцессоры 9. В чем наиболее существенная разница между следующими двумя фрагментами: а) LDT #8 CLEAR X LOOP • TIXR Т JLT LOOP б) &CTR SET 0 WHILE (&CTR LT 8) &CTR SET &CTR + 1 ENDW 10. Используя макроопределения на рис. 4.9а, получите текст макро- расширений для следующих предложений макроинициализации: a) RDBUFF Fl,BUFFER, LENGTH,(04,12) 6) LABEL RDBUFF Fl, BUFFER, LENGTH,00 в) RDBUFF Fl,BUFFER,LENGTH Какое значение будет иметь функция %NITEMS(&EOR) в последних двух случаях? 11. Дополните свой ответ к упр. 7 так, чтобы включить предложение WHILE. Можете не заботиться о вложенных конструкциях WHILE. 12. Дополните свой ответ к упр. 11 так, чтобы допустить вложенные конструкции WHILE. 13. Переменные периода макрогенерации обычно рассматриваются как локальные для данного макроопределения. Таким образом, значение, при- своенное переменной периода макрогенерации, может использоваться только внутри того же самого макроопределения. В некоторых случаях, однако, было бы удобно разрешить использование одной и той же переменной пе- риода макрогенерации внутри двух связанных между собой макроопреде- лений. Как это может быть реализовано? 14. Модифицируйте приведенный на рис. 4.5 алгоритм с тем, чтобы включить в него обработку ключевых параметров макроопределений. 15. Некоторые макропроцессоры допускают использование предложений макроинициализации, в которых одни параметры являются ключевыми, а другие — позиционными. Каким образом макропроцессор может обработать такого рода предложения макроинициализации? 16. Каким образом можно было бы задать значения по умолчанию для позиционных параметров? Какие изменения в алгоритме на рис. 4.5 при- шлось бы сделать для введения такой возможности? 17. Вспомним макроопределение RDBUFF на рис. 4.8а. Каждое из ни- жеследующих предложений макроинициализации этого макроопределения содержит ошибку. Какие из этих ошибок будут выявлены макропроцессо- ром, а какие — ассемблером? a) RDBUFF F3,BUF,RECL,ZZ (неправильное значение для &EOF) б) RDBUFF F3,BUF,RECL, 04, 2048,01 (слишком много аргументов)
Упражнения 221 в) RDBUFF F3,, RECL,04 (не определено значение для &BUFADR) г) RDBUFF F3,RECL,BUF (неправильный порядок аргументов) Раздел 4.3 1. Предположим, что макропроцессор с логикой работы, аналогичной изображенной на рис. 4.5, должен обеспечивать рекурсивную макрогенера- цию. При обсуждении было выявлено, что значения переменной EXPANDING и ARGTAB должны запоминаться при рекурсивных вызовах процедуры EXPAND. Какие другие значения также необходимо запоминать для раз- ных способов реализации алгоритма? 2. Каким образом рекурсивный макропроцессор может быть реализован на языке макропроцессора? 3. Можно ли в нерекурсивном макропроцессоре допустить появление предложений макроинициализаций внутри макроопределений? Каковы будут преимущества и недостатки такого подхода? 4. Выберите два знакомых вам языка программирования высокого уровня. Какие особенности этих языков будут существенны при реализации макропроцессора для них? 5. Выберите какой-либо знакомый вам язык высокого уровня и язык ассемблера. Какие особенности этих двух языков будут существенны для реализации макропроцессора для них? 6. Опишите алгоритм взаимодействия макропроцессора, реализующего режим «строка в строку», с ассемблером. 7. Перечислите вспомогательные функции и процедуры, которые могли бы быть общими для ассемблера и встроенного макропроцессора.
Глава 5. Компиляторы В этой главе мы обсудим структуру и функции компилято- ров с языков программирования высокого уровня. Множество учебных пособий целиком посвящено обсуждению компилято- ров. Мы не можем даже надеяться обсудить этот вопрос со всей полнотой в одной главе. Вместо этого мы введем наиболее важные концепции и понятия, связанные с компиляторами, а также проиллюстрируем наше изложение рядом примеров. По- сле обсуждения каждой темы мы дадим ссылки для тех чита- телей, кто захочет более подробно разобраться в этой пробле- матике. В разд. 5.1 описаны основные функции простого однопросмо- трового компилятора. Мы продемонстрируем работу такого ком- пилятора путем прослеживания процесса трансляции простого программного фрагмента от начала и до конца. Этот раздел содержит более детальную информацию, чем другие разделы этой главы, поскольку обсуждаемые понятия имеют фундамен- тальное значение. В разд. 5.2 обсуждаются машинно-зависимые расширения базовой схемы, представленной в разд. 5.1. Эти расширения относятся в основном к генерации объектного кода и оптими- зации. В разд. 5.3 описаны некоторые машинно-независимые дополнения к базовой схеме. В разд. 5.4 описаны некоторые варианты построения компи- ляторов. К ним относятся многопросмотровые компиляторы, ин- терпретаторы, компиляторы на P-код и компиляторы компиля- торов. В заключение главы в разд. 5.5 приведены четыре при- мера реальных компиляторов и систем построения компилято- ров, которые обсуждаются на базе концепций, введенных в предыдущих разделах. 5.1. Основные функции компилятора В этом разделе обсуждаются операции, необходимые для компиляции программ типичного языка высокого уровня. В ка- честве примера мы будем использовать программу на Паскале, изображенную на рис. 5.1, однако обсуждаемые концепции и подходы приложимы к компиляции программ и с других языков.
5.1. Основные функции компилятора 223 Для облегчения построения компиляторов язык высокого уровня обычно описывается в терминах некоторой грамматики. Эта грамматика определяет форму (синтаксис) допустимых предложений языка. Например, оператор присваивания может быть определен в грамматике как имя переменной, за которой следует оператор присваивания (:=), за которым следует вы- ражение. Проблема компиляции может быть сформулирована как проблема поиска соответствия написанных программистом предложений структурам, определенным грамматикой, и гене- рации соответствующего кода для каждого предложения 1 PROGRAM STATS 2 VAR 3 SUM,SUMSQ,I,VALUE,NEAN,VARIANCE s INTEGER A BEGIN 5 SUM :='0; 6 SUMSQ : = 0; 7 FOR I := 1 TO 100 DO S BEGIN 9 READ.iVALUE); 50 SUM s= SUM + VALUE? 3.1 SUMSQ SUMSQ + VALUE M VALUE 52 END; 13 MEAN := SUM DIU 100? 54 VARIANCE SUMSQ DIV 100 - MEAN * MEAN J 15 WRIТЕ(MEAN,VARIANCE) 16 END. Рис. 5.1. Пример программы на Паскале. Предложения исходной программы удобнее представлять в виде последовательности лексем (tokens), чем просто как стро- ку символов. Лексемы можно понимать как фундаментальные кирпичики, из которых строится язык. Например, лексемой мо- жет быть ключевое слово, имя целой переменной, арифметиче- ский оператор и т. д. Просмотр исходного текста, распознава- ние и классификация различных лексем называются лексиче- ским анализом. Часть компилятора, которая выполняет эту функцию, обычно называют сканером 2>. Как только лексемы выделены, каждое предложение про- граммы может быть распознано как некоторая конструкция языка, как, например, декларативные операторы или оператор присваивания, описанные с помощью грамматики. Процесс, на- зываемый синтаксическим анализом или синтаксическим разбо- ром, осуществляется той частью компилятора, которая обычно называется синтаксическим анализатором (parser). Последним шагом базовой схемы процесса трансляции является генерация у Автор придерживается термина «компилятор» и не применяет более щироко используемый у нас термин «транслятор». — Прим. ред. В нашей литературе принят термин «лексический анализатор». —
124 Гл. 5. Компиляторы объектного кода. Большинство компиляторов генерирует непо- средственно программу в машинных кодах, а не программу на ассемблере, предназначенную для последующей трансляции в машинные коды. Хотя мы указали на три основных шага в процессе компи- ляции — лексический анализ, синтаксический анализ и генера- цию кодов,— важно отметить, что компилятор вовсе не обязан делать три просмотра транслируемой программы. Для некото- рых языков возможна компиляция программы за один про- смотр. В этом разделе мы обсудим, как может работать одно- просмотровый компилятор. Однако компиляторы для других языков и компиляторы, осуществляющие изощренную оптимиза- цию объектного кода или какой-либо другой анализ програм- мы, обычно являются многопросмотровыми. Мы обсудим разбие- ние процесса компиляции на отдельные просмотры в разд. 5.4. В разд. 5.5 приводится несколько примеров построения реаль- ных компиляторов. В последующих разделах мы обсудим основные элементы процесса компиляции и проиллюстрируем их на примере про- граммы, представленной на рис. 5.1. В разд. 5.1.1 вводятся ос- новные понятия и обозначения, используемые для описания грамматик языков программирования. В разд. 5.1.2—5.1.4 об- суждаются функции лексического анализа, синтаксического ана- лиза и генерации кодов. 5.1.1. Грамматики п Грамматика языка программирования является формальным описанием его синтаксиса или формы, в которой записаны от- дельные предложения программы или вся программа. Грамма- тика не описывает семантику или значения различных предло- жений. Информация о семантике содержится в программах ге- нерации объектного кода. В качестве иллюстрации разницы между синтаксисом и семантикой рассмотрим два предложения: I := J + К и I:=X + Y где X и Y являются действительными переменными, a I, J, К — целыми переменными. Эти два предложения имеют оди- наковый синтаксис. Оба являются операторами присваивания, в которых присваиваемое значение определяется выражением, состоящим из двух имен переменных, разделенных оператором сложения. Однако семантика этих двух предложений совершен- В некоторых работах под грамматикой понимают понятие, объеди- няющее синтаксис и семантику. — Прим. ред.
5.1. Основные функции компилятора 225 но различна. Первое предложение говорит о том, что перемен- ные в выражении должны быть сложены с использованием целых арифметических операций, а результат сложения должен быть присвоен переменной I. Второе предложение задает сло- жение с плавающей точкой, результат которого должен быть преобразован в целое число перед присваиванием. Очевидно, эти два предложения будут скомпилированы в совершенно раз- личные последовательности машинных команд, хотя их грам- матическое описание одинаково. Различия между ними про- явятся на этапе генерации объектного кода. 1 (prog) : - PROGRAM (prog-name) VAR (dec-list) BEGIN (stmt-list) END 2 <prog-name) : — id 3 {dec-list) = (dec) | (dec-list) ; (dec) 4 {dec) : = (id-list) : (type) 5 (type) • — INTEGER 6 (id-list) = id 1 (id-list) , id 7 (stmt-list) : = (stmt) j (stmt-list) ; (stmt) 8 (stmt) : = (assign) | (read) | (write) | (for) 9 (assign) : = id (exp) 1а ' (exp) : (term) 1 (exp) + (term) | (exp) - (term) 11 (term) : = (factor) j (term) * (factor) | (term) DIV (factor) 12 г (factor)’ : = id I iat | ( (exp) ) 13 (read) READ ( (id-list) ) 14 (write) = WRITE ( (id-list) ) 15 (for) : = FOR (index-exp) DO (body) 16 (index-exp) : = id : = (exp) TO (exp) < 17 (body) : (stmt) ] BEGIN (stmt-list) END Рис. 5.2. Упрощенная грамматика Паскаля. Существует несколько различных форм записи грамматик/ среди которых мы рассмотрим форму Бекуса — Наура (БНФ). БНФ не самое мощное из известных средств описания синтак- сиса. Фактически она даже не является вполне адекватной для описания некоторых реально существующих языков программи- рования. Однако эта форма достаточно проста, широко исполь- зуется и предоставляет достаточные для большинства при- ложений средства. На рис. 5.2 изображена одна из возможных грамматик БНФ для очень узкого подмножества языка Пас- каль. Полное описание грамматики БНФ для Паскаля содер- жится в книге Йенсен [1974]. В этом разделе нам осталось обсудить эту грамматику и показать, как она связана с приме- ром программы, изображенным на рис. 5.1. Грамматика БНФ состоит из множества правил вывода, каждое из которых определяет синтаксис некоторой конструк- ции языка программирования. Рассмотрим, например, правило 13 на рис. 5.2: (read) READ ( (id - list) ) Это определение синтаксиса предложения READ языка Паскаль, обозначенное в грамматике как (read). Символ можно 8 Зак. 792
226 Гл. 5. Компиляторы читать как «является по определению». С левой стороны от этого символа находится определяемая конструкция языка (в нашем случае— (read)), а с правой — описание синтаксиса этой конструкции. Строки символов, заключенные в угловые скобки ( и ), называются нетерминальными символами (т. е. являются именами конструкций, определенными внутри грамматики). То, что не заключено в угловые скобки, называется терминальными символами грамматики (лексемами). В этом правиле вывода нетерминальными символами являются (read) и (id—list). Тер- минальными символами являются лексемы READ, (,). Таким образом, это правило определяет, что конструкция <read> состоит из лексемы READ, за которой следует лексема (, за ней следует конструкция языка, называемая (id—list), за ко- торой следует лексема ). Пробелы при описании грамматических правил не существенны и вставляются только для наглядности. Для распознавания нетерминального символа (read) необ- ходимо чтобы было определение для нетерминального символа <id—list). Это определение дается правилом 6 на рис. 5.2: (id-list) ::= id | (id-list) , id Это правило предлагает две возможности, разделенные симво- лом |, для синтаксиса нетерминального символа (id—list). Первая возможность состоит в том, что (id—list) состоит просто из лексемы id (запись id означает идентификатор, рас-* познаваемый сканером). Другой вариант состоит в том, что (id—list) состоит из (id—list), за которым следует лексема «,», за которой следует лексема id. Обратите внимание, что это правило является рекурсивным. Это означает, что конструкция (id—list) определяется фактически в терминах себя самой. Рассмотрев несколько примеров, вы убедитесь, что в соответ- ствии с этим правилом, нетерминальным символом (id—list) является любая последовательность из одной или более лексем id, разделенных запятыми. Таким образом, ALPHA является нетерминальным символом (id—list), состоящим из единственного идентификатора ALPHA; ALPHA , BETA также являются конструкцией (id—list), состоящей из конст* рукции (id—list) ALPHA, за которой следует «,», за которой идет идентификатор ВЕТА и т. д. Результат анализа исходного предложения в терминах грам- матических конструкций удобно представлять в виде дерева. Т<дае деревья обычно называют деревьями грамматического
5.1. Основные функции компилятора 227 (assign) Рис. 5.3. Дерево грамматического разбора для двух предложений программ мы на рис, 5,1, разбора или синтаксическими деревьями. На рис. 5.3а изобра- жено дерево грамматического разбора для предложения READ (VALUE) с использованием только что обсужденных двух правил вы- вода. Правило вывода 9 на рис. 5.2 дает определение синтаксиса предложения присваивания: (assign) id := (exp) 8*
PROGRAM (pfog-name) VAR. (dec-list) BEGIN END, > id {STATS} (dec) (type) (id-list) I {VARIANCE} (id-list) INTEGER (stmt-list) (stmt-list) (stmt-list) (stmt) (stmt) (id-list) {VALUE} (id-list) (id-list) {SUMSQ} id {1} (id-list) id {SUM} id {MEAN} (stmt-list) ; (stmt-list) ; (stmt) (stmt) (stmt-li§t) (stmt) (assign) {SUM} (exp) (term) (factor) iat {0} (stmt) (assign) {VARIANCE} (?xp> id {MEAN} (assign) {SUMSQ} (exp) (term) (factor) iat {0} (term) (write) ( (id-list) ) WRITE (assign) (exp) (term) (exp) {VARIANCE} (id-list) (term) id । {MEAN} (term) * (factor) (term) .DIV (factor)(term) DIV (factor) (factor) (factor) id {SUM} id {MEAN} iat (factor) {100} I iat id {100} {MEAN} id {SUMSQ}
г FOR (for) {index-exp) do <body) TT“ {exp} {term} (factor) T~1 I----------1---------1 TO (exp) BEGIN (stmt-list) END (term) (stmt-list) ; {stmt} (factor) (stmt-list) i (stmt) (assign) <1} iat {stmt} (assign) id (exp> {100} 1 । {SUMSQ} I (read) id ;=? (exp) I {SUM} I r~i—rh rn—i READ ( (id-list) ) (exp) + (term) i iexp) (term) I id {VALUE} (term) 1 (factor) I id {SUM} - - (factor) I id {VALUE} I (factor) J id {SUMSQ} + (term) Hnr (term) * I (factor) id {VALUE} (factor), I id {VALUE} Рис, 5.4, Дерево грамматического разбора для программы на рис. 5.1,
230 Гл. 5. Компиляторы Это означает, что конструкция (assign) состоит из лексемы id, за которой следует лексема :=, за которой идет конструкция \ехр). Правило 10 дает определение конструкции (ехр): (ехр) ::= (term) | (exp) + (term) | (exp) —(term) Используя те же соображения, что и при анализе конструкции (id—list), мы видим, что это правило определяет конструкцию (ехр) как состоящую из любой последовательности конструкций (term), соединенных операторами плюс или минус. Анало- гично правило 11 определяет конструкцию (term) как последо- вательность конструкций (factor), разделенных, знаками * или DIV. В соответствии с правилом 12 конструкция (factor) мо- жет состоять из идентификатора id или целого int, которое также распознается сканером, или из конструкции (ехр), за- ключенной в круглые скобки. На рис. 5.36 изображено дерево грамматического разбора для предложения 14 на рис. 5.1, основанное на только что рас- смотренных правилах вывода. Вы должны внимательно изучить этот рисунок, чтобы убедиться в правильном понимании того, как выполняется грамматический анализ исходного предложе- ния на основании введенных грамматических правил. В разд. 5.1.3 мы обсудим методы осуществления подобного син- таксического анализа компиляторами. Обратите внимание, что в соответствии с деревом граммати- ческого разбора на рис. 5.36 умножение и деление производятся перед сложением или вычитанием. Прежде всего должны вы- числяться термы SUMSQ DIV 100 и MEAN * MEAN, по- скольку эти промежуточные результаты являются операндами ;('(левым и правым поддеревом) операции вычитания. Иначе то же самое можно выразить, сказав, что операции умножения и деления имеют более высокий ранг (precedence), чем операции сложения и вычитания. Такое ранжирование операций является следствием того, как записаны правила 10—12 (см. упр. 5.1.3). В разд. 5.1.3 мы увидим, как подобное ранжирование может быть использовано при осуществлении процесса грамматическое го разбора. Дерево грамматического разбора на рис. 5.3 представляет собой единственно возможный результат анализа этих двух предложений в терминах грамматики, изображенной на рис. 5.2. Для некоторых грамматик подобной единственности может не существовать. Если для одного и того же предложения можно построить несколько различных деревьев грамматического раз- бора, то соответствующая грамматика называется неоднознач- ной (ambiguous). При разработке компиляторов обычно пред- почитают пользоваться однозначными грамматиками, поскольку в некоторых случаях неоднозначная грамматика оставляет со- мнения относительно того, какой объектный код должен быть
5.1. Основные функции компилятора 231 сгенерирован для анализируемого предложения (см., например, упр. 5.1.4). На рис. 5.4 изображено дерево грамматического разбора для всей программы рис. 5.1. Вы должны внимательно проана- лизировать этот рисунок, с тем чтобы понять, каким образом форма записи и структура программы соответствуют правилам вывода грамматики, приведенной на рис. 5.2. 5.1.2. Лексический анализ Лексический анализ включает в себя сканирование компи- лируемой программы и распознавание лексем, составляющих предложения исходного текста. Сканеры обычно строятся таким образом, чтобы они могли распознавать ключевые слова, опе- раторы и идентификаторы так же, как целые числа, числа с плавающей точкой, строки символов и другие аналогичные кон- струкции, встречающиеся в исходной программе. Точный пере- чень лексем, которые необходимо распознавать, зависит, разу- меется, от языка программирования, на который рассчитан компилятор, и от грамматики, используемой для описания это- го языка. Такие объекты, как идентификаторы и целые числа, обычно распознаются сканером как отдельные лексемы. Од- нако возможен и другой подход, в рамках которого эти лексе- мы описываются правилами грамматики. Например, идентифи-. катор может быть определен следующим набором правил; (ident) ::= (letter) | (ident) (letter) | (ident) (digit) (letter) ::=A |B|C|D| ... |Z (digit) ::= 0 | 1 [ 2 | 3 | ... | 9 В этом случае сканер будет распознавать в качестве отдельных лексем единичные символы А, В, 0, 1 и т. д. Далее в процессе грамматического разбора последовательность этих символов будет интерпретирована как конструкция языка (ident). Однако в рамках такого подхода распознавание простых идентифика- торов должны осуществлять общие алгоритмы грамматического разбора (например, описанные в следующем разделе). Специа- лизированные программы сканера могут выполнить эту функ- цию гораздо более эффективно. Поскольку основная часть про- граммы состоит из подобных многосимвольных идентификато- ров, сокращение времени компиляции может быть весьма суще- ственным. Кроме того, такие ограничения, как максимальная длина идентификатора, гораздо легче учесть в сканере, чем в общих алгоритмах грамматического разбора. Аналогично сканер обычно распознает непосредственно как односимвольные, так и многосимвольные лексемы. Например, строку символов READ предпочтительнее рассматривать как
232 Гл. 5. Компилятору отдельную лексему, нежели чем последовательность, состоящую из четырех лексем R, Е, A, D. Строка := будет распознана как оператор присваивания, а не как символ за которым следует =. Возможен, конечно, и такой подход, когда много- символьные лексемы рассматриваются как состоящие из от- Лексема Код PROGRAM 1 VAR 2 BEGIN 3 END 4 END 5 INTEGER 6 FOR 7 READ 8 WRITE 9 TO 10 DO 11 ; 12 : 13 . 14 :«= 15 + 16 17 * 18 DIV 19 ( 20 ) 21 la 22 ini 23 Рис. 5.5. Коды лек- сем для грамматики на рис. 5.2. дельных лексем — символов, но это сущест- венно усложняет процесс грамматического разбора. Результатом работы сканера является последовательность лексем. Для повыше- ния эффективности последующих действий каждая лексема обычно представляется некоторым кодом фиксированной длины (например, целым числом), а не в виде строки символов переменной длины. При подобной записи для грамматики, приве- денной на рис. 5.2, лексеме PROGRAM соответствует целое число 1, идентифика- тору id соответствует число 22 и т. д. Если распознанная лексема является ключевым словом или оператором, такая схема кодирования дает всю необходимую информацию. В случае же идентификато- ра дополнительно необходимо конкретное имя распознанного идентификатора. То же относится к целым числам, числам с пла- вающей точкой, строкам символов и т. д. Этого можно добиться за счет хранения не только кода лексемы соответствующего типа, но и дополнительного специфика- тора лексемы. Спецификатор должен со- держать имя идентификатора, значение целого числа и т. д.— всю информацию, распознанную сканером. Некоторые сканеры устроены та- ким образом, что записывают идентификатор, когда он встретился впервые, в таблицу символов, аналогичную таблице символов, используемой в ассемблере. В этом случае спецификатором лексем-идентификаторов может служить ука- затель на соответствующий элемент таблицы символов, что по- зволяет избежать дополнительного поиска по таблицам на по- следующих этапах компиляции. На рис. 5.6 изображен результат обработки сканером про- граммы, приведенной на рис. 5.1, с использованием кодировки лексем, представленной на рис. 5.5. Для лексем типа 22 (иден- тификаторы) соответствующими спецификаторами являются указатели на таблицу символов (обозначаемые "SUM, "SUMSQ и т. д.). Для лексем, имеющих тип 23 (целые числа),
5.1. Основные функции компилятора 233 Строка Тип лексемы Спецификатор лексемы Строка Тил лексемы Спецификатор лексемы 1 1 10 22 -SUM 22 -STATS 15 2 2 22 -SUM 3 22 -sum 16 14 22 -VALUE 22 -SUMSQ 12 14 11 22 -SUMSQ 22 -I 15 ‘14 22 -SUMSQ 22 -VALUE 16 14 22 -VALUE 22 -MEAN 18 14 22 -VALUE 22 -VARIANCE 12 4 13 13 22 -MEAN 6 15 4 3 22 -sum 5 22 -sum 19 15 23 #100 23 #0 12 12 14 22 -VARIANCE 6 22 -sumsg 15 15 22 -SUMSQ 23 #0 19 12 23 #100 7 7 17 22 -I 22 -MEAN 15 18 23 #1 22 -MEAN 10 12 23 #100 15 9 11 20 б 3 22 -MEAN 9 8 14 20 22 -VARIANCE 22 -VALUE 21 21 16 5 12 Рис. 5.6. Результат лексического разбора программы на рис. спецификаторами являются значения соответствующих чисел (обозначаемые *0, *100 и т. д.). Мы изобразили результат работы сканера в виде списка кодов лексем; однако это вовсе не означает, что сканер обраба- тывает целиком всю программу до начала каких-либо других действий. Чаще сканер является процедурой, вызываемой в
234 Гл. 5. Компиляторы процессе грамматического разбора для получения очередной лексемы. В этом случае результатом каждого вызова сканера будет код очередной лексемы исходной программы (и, если необходимо, ее спецификатор). При этом задача сохранения всей информации о лексемах, которая может понадобиться впо- следствии, ложится на процесс грамматического разбора. По- добный пример работы сканера приведен в следующем раз- деле. В дополнение к своей основной функции — распознаванию лексем — сканер обычно также выполняет чтение строк исход- ной программы и, возможно, печать листинга исходной про- граммы. Комментарии игнорируются сканером, за исключением того случая, когда они должны быть напечатаны и, таким об- разом, эффективно удаляются из исходной программы до на- чала процесса грамматического разбора. Процесс лексического разбора в том виде, как мы его опи- сали, является весьма простым. Однако многие языки имеют специфические особенности, которые должны учитываться при программировании сканера. Например, сканер должен учиты- вать информацию, связанную с соглашениями по формату рас- положения строк исходной программы. Так, в языке Фортран число в колонках 1—5 в исходном предложении должно рас- сматриваться как номер предложения, а не как целое число. Сканер должен учитывать также следующую языково-зависи- мую информацию: являются ли пробелы ограничителями лексем '(как в Паскале) или нет (как в Фортране), могут ли предло- жения свободно размещаться в нескольких строках входного текста (как в Паскале) или же для этого требуются специаль- ные признаки продолжения (как в Фортране). Правила распознавания лексем могут различаться в разных частях программы. Так, например, фрагмент READ не должен распознаваться в качестве ключевого слова, если он встречает- ся внутри строки символов, заключенной в кавычки. Пробелы же оказываются существенными именно внутри таких закавы- ченных строк, даже если они не существенны в остальных ча- стях программы. Аналогично в программе на Фортране строка F6.3 должна интерпретироваться по-разному в зависимости от того, расположена ли она в предложении FORMAT или в ка- ком-то другом месте программы. В некоторых языках встречаются также нетипичные случаи, которые должны учитываться сканером. Например, в предло- жении на языке Фортран DO 10 1=1, 100 сканер должен распознавать DO как ключевое слово, 10 как номер предложения, I как идентификатор и т. д. Однако в
5.1. Основные функции компилятора 235 предложении DO 10 1 = 1 сканер должен распознать DO 10 I как идентификатор, а все предложение как предложение присваивания переменной DO10I значения 1. В этом случае сканер должен посмотреть вперед, есть ли запятая, прежде чем он сможет сделать вывод о правильной интерпретации фрагмента предложения DO. Язы- ки, в которых зарезервированные слова могут употребляться в другом смысле, представляют для сканера еще больше труд- ностей. В ПЛ/1, например, любое ключевое слово также мо- жет использоваться в качестве идентификатора. Такие слова, как IF, THEN, ELSE, могут являться как ключевыми словами, так и именами переменных, определенных программистом. Так, например, предложение IF THEN = ELSE THEN IF = THEN; ELSE THEN = IF является формально правильным, хотя и весьма экзотическим предложением ПЛ/1. В этом случае сканер должен как-то взаимодействовать с процессом грамматического разбора, что- бы правильно интерпретировать каждое слово, или* же он мо- жет считать, что идентификаторы и ключевые слова являются элементами одного и того же класса лексем, оставляя задачу их различения процессу грамматического разбора. В настоящее время разработан ряд средств для автомати- ческого конструирования лексических анализаторов по спе- цификациям, записанным на специальном языке. Описание од- ного из таких средств, обладающих возможностями обработки рассмотренных выше нестандартных ситуаций, содержится в работе Ахо [1977]. 5.1.3. Синтаксический анализ Во время синтаксического анализа предложения исходной программы распознаются как языковые конструкции, описывае- мые используемой грамматикой. Мы можем рассматривать этот процесс как построение дерева грамматического разбора для транслируемых предложений. Методы грамматического разбора можно разбить на два больших класса — восходящие и нисхо- дящие — в соответствии с порядком построения дерева грам- матического, разбора. Нисходящие методы (методы сверху вниз) начинают с правила грамматики, определяющего конеч- ную цель анализа с корня дерева грамматического разбора, и пытаются так его наращивать, чтобы последующие узлы дерева соответствовали синтаксису анализируемого предложения. Вос- ходящие методы (методы снизу вверх) начинают с конечных узлов дерева грамматического разбора и пытаются объединить
236 Гл. 5. Компиляторы. их построением узлов все более и более высокого уровня до тех пор, пока не будет достигнут корень дерева. Разработано множество методов грамматического разбора, большинство из которых применимо лишь к грамматикам, удовлетворяющим определенным требованиям. В этом разделе мы кратко опишем один нисходящий и один восходящий ме- тод, а также продемонстрируем применение этих методов к на- шему примеру программы. Мы не собираемся описывать все детали обоих методов. Вместо этого мы попытаемся продемон- стрировать общий подход и снабдить ссылками читателей, же- лающих изучить эти методы более подробно. Восходящий метод грамматического разбора, который мы рассмотрим, называется методом операторного предшествова- ния. Он основан на анализе пар последовательно расположен- ных операторов исходной программы и решении вопроса о том, какой из них должен выполняться первым. Рассмотрим, напри- мер, арифметическое выражение А + В * С - D В соответствии с обычными правилами арифметики умножение и деление осуществляются до сложения и вычитания. Можно сказать, что умножение и деление имеют более высокий уро- вень предшествования, чем сложение и вычитание. При анализе первых двух операторов (+ и «) выяснится, что оператор имеет более низкий уровень предшествования, чем оператор *. Часто это записывают следующим образом: + <• * Аналогично для следующей пары операторов (* и —) опера- тор * имеет более высокий уровень предшествования, чем опе- ратор —. Мы можем записать это в виде * • > — Метод операторного предшествования использует подобные отношения между операторами для управления процессом грамматического разбора. В частности, для рассмотренного арифметического выражения мы получили следующие отноше- ния предшествования: А+ В*С —D Отсюда следует, что подвыражение В*С должно быть вычис- лено до обработки любых других операторов рассматриваемого выражения. В терминах дерева грамматического разбора это означает, что операция * расположена на более низком уровне узлов дерева, чем операции + или —. Таким образом, рассмат-
5.1. Основные функции компилятора 237 риваемый метод грамматического разбора должен распознать конструкцию В * С, интерпретируя ее в терминах заданной грамматики, до анализа соседних термов предложения. Предшествующее изложение иллюстрирует основную идею, на которой основан метод грамматического разбора, построен- ный на отношениях операторного предшествования. В рамках этого метода анализируемое предложение сканируется слева направо до тех пор, пока не будет найдено подвыражение, опе- раторы которого имеют более высокий уровень предшествова- ния, чем соседние операторы. Далее это подвыражение распо- знается в терминах правил вывода используемой грамматики. Этот процесс продолжается до тех пор, пока не будет достиг- нут корень дерева, что и будет означать окончание процесса грамматического разбора. Далее мы рассмотрим приложение описанного подхода к нашему примеру программы. Первым шагом при разработке процессора грамматического разбора, основанного на методе операторного предшествования, должно быть установление отношений предшествования между операторами грамматики. При этом под оператором понимается любой терминальный символ (т. е. любая лексема). Таким об- разом, мы должны, в частности, установить отношение предше- ствования между лексемами BEGIN, READ, id и (. Матрица на рис. 5.7 задает отношения предшествования для грамматики, приведенной на рис. 5.2. Каждая клетка этой матрицы опреде- ляет отношение предшествования (если оно существует) между лексемами, соответствующими строке и столбцу, на пересечении которых находится эта клетка. Например, мы видим, что PROGRAM = VAR и BEGIN < FOR Отношение = означает, что обе лексемы имеют одинаковый уровень предшествования и должны рассматриваться граммати- ческим процессором в качестве составляющих одной конструк- ции языка. Обратите внимание, что для отношения предше- ствования не выполняются некоторые правила, привычные для отношения арифметического порядка. Например, ; •> END но END > ; Обратите внимание также на то, что для многих пар лексем отношение предшествования не существует. Это означает, чтб соответствующие пары лексем не могут находиться рядом ни в каком грамматически правильном предложении, Если подобг ная комбинация лексем все же встретится в процессу
238 Гл. 5. Компиляторы грамматического разбора, то она должна рассматриваться как синтаксическая ошибка. Существуют алгоритмы автоматического построения матриц предшествования, подобных изображенной на рис. 5.7, на осно- ве описания грамматики (см., например, Ахо [1977]). Для при- менимости метода операторного предшествования необходимо, Рис. 5.7. Матрица предшествования для грамматики на рис. 5.2. чтобы отношения предшествования были заданы однозначно. Например, не должно быть одновременно отношений ;<• BEGIN и ; •> BEGIN. Это требование выполняется для граммати- ки рис. 5.2, однако надо иметь в виду, что несущественные ее изменения могут привести к тому, что некоторые из отношений перестанут быть однозначными и метод оперативного предше- ствования станет неприменимым. На рис. 5.8 изображен результат применения метода опера- торного предшествования к двум предложениям программы рис. 5.1. На рис. 5.8а изображен анализ предложения READ строки 9 этой программы. Это предложение анализируется по
5.1. Основные функции компилятора 239 лексемам слева направо. Для каждой пары соседних операторов определено отношение предшествования. В строке (i) на рис. 5.8а процессор грамматического разбора выделил фраг- мент, ограниченный отношениями <• и •>, для распознавания в терминах грамматики. В данном случае этот фрагмент со- держит единственную лексему id. Эта лексема может быть рас- познана как нетерминальный символ (factor) в соответствии с грамматическим правилом 12. Однако эта лексема может быть также распознана как нетерминальные символы <prog — name) (правило 2) и <id — list) (правило 6). Для рассматриваемого метода не важно, какой конкретно нетерминальный символ рас- познан. Лексема id интерпретируется просто как некоторый нетерминальный символ <Ni>. В строке (ii) на рис. 5.8а изо- бражен результат анализа предложения, в котором лексема id заменена на <Ni>. Из него следует, что далее надо распозна- вать правый фрагмент предложения. Строка (ii) на рис. 5.8а также изображает отношение пред- шествования для новой версии анализируемого предложения. Процессор грамматического разбора, основанный на отношении предшествования, обычно использует стек для хранения полу- ченных от сканера (но еще грамматически не распознанных) лексем для их последующего распознавания. Отношения пред- шествования определены лишь для терминальных символов. Таким образом, нетерминальный символ <Ni> не будет вовле- чен в этот процесс, и далее отношения предшествования будут установлены между терминальными символами ( и ). В данном случае для последующего распознавания будет выделен фраг- мент READ ( W ) который соответствует, за исключением имени нетерминального символа, грамматическому правилу 13. Это правило является единственным, применимым для этого фрагмента. Однако, так же как и прежде, мы просто обозначим этот фрагмент как не- терминальный символ <N2>. На этом разбор предложения READ закончен. Если мы сравним дерево грамматического разбора на рис. 5.3а с только что построенным, то увидим, что они полностью совпадают, за исключением используемых имен нетерминальных символов. Это означает, что мы правильно определили синтаксис анализируе- мого предложения, что и является целью процесса грамматичен ского разбора. Сами же имена нетерминальных символов были выбраны произвольно автором записи грамматики и не имеют никакого отношения к смыслу предложений исходной про- граммы. На рис. 5.86 изображен аналогичный пошаговый процесс грамматического разбора предложения присваивания в строке
240 Гл. 5. Компиляторы (i) ... BEGIN READ ( id. ) < & < > (ii) ... BEGIN • READ ( .(NJ J J (iii) ... BEGIN (Ng) a (I) ... Is idg DIV < • * < > pl) ... idL |« (NJ DIV ini - < i < < > (ill) ... idx (NJ div (n2> - < & < > (M ...id! :» (N3> - idj • <4 di < < > (NJ I id {VALUE} <n2> I I—I—‘ BEAD ( (Nj I id {VALUE} (NJ idg {SUMSQ} Wi> <N2> id2 ini {SUMSQ} {100} <N3> I 1 (NJ (N2> ld2 DIV ini {SUMSQ} {100} (N3) M ... idj :» <N3} - (H4> • U4 ; < =» <5 i> (nJ (n2} ld2 DIV int {SUMSQ} {100} <N4) I id3 {MEAN} (Vl) ... idj (N3> - (N4) < Й <$. * <n5> <N3) r”H <n2> <n4) <n5> ld2 DIV ini ld3 ld4 {SUMSSl {100} {MEAN} {MEAN}
5.1. Основные функции компилятора 241 (vii) <N3) - <N6) r3> <N6> <Ni> <N2} .Г id2 biv int id3 * id4 {SUMSQ} {100} {MEAN} {MEAN} (viii) ...id! := <N?) (N3) <N7) <N2> (N4> (N5> id2 DIV int - id3 * 1йл {SUMSQ} {100} {MEAN} {MEAN} Рис. 5.8. Грамматический разбор двух предложений программы на рис. 5.1 методом операторного предшествования. 14 программы на рис. 5.1. Обратите внимание, что процесс ска-' нирования слева направо продолжается на каждом шаге грам- матического разбора лишь до тех пор, пока не определился очередной фрагмент предложения для грамматического распо- знавания, т. е. первый фрагмент, ограниченный отношениями < • и • > . Как только подобный фрагмент выделен, он интер- претируется как некоторый очередной нетерминальный символ в соответствии с каким-нибудь правилом грамматики, Этот
242 Гл. 5. Компиляторы процесс продолжается до тех пор, пока предложение не будет распознано целиком. Вы должны внимательно проследить все этапы, изображенные на рис. 5.86, для того, чтобы убедиться в правильном понимании процесса анализа с использованием матрицы предшествования, приведенной на рис. 5.7. Обратите внимание, что каждый фрагмент дерева грамматического раз- бора строится, начиная с оконечных узлов вверх, в сторону корня дерева. Отсюда и возник термин восходящий разбор. Сравнивая деревья грамматического разбора, изображенные на рис. 5.86 и рис. 5.36, мы обнаружим некоторые различия. Например, идентификатор SUMSQ на рис. 5.3 был сначала интерпретирован как (factor), а потом как (term), являющийся одним из операндов операции DIV. На рис. 5.86 идентификатор SUMSQ интерпретирован как единственный нетерминальный символ (Ni), являющийся операндом DIV. Таким образом, (Ni) на дереве, изображенном на рис. 5.86, соответствует двум нетерминальным символам — (factor) и (term) — на рис. 5.3б< Имеются и другие подобные различия между этими двумя де- ревьями. Они вытекают из свободы образования имен нетерминаль- ных символов, распознаваемых в рамках метода операторного предшествования. Интерпретация SUMSQ на рис. 5.36 сначала в качестве (factor), а потом (term) является просто переимено- ванием нетерминальных символов. Такое переименование необ- ходимо, поскольку в соответствии с грамматическим правилом 11 первым операндом операции умножения должен быть (term), а не (factor). Так как для метода операторного предшествова- ния имена нетерминальных символов несущественны, то подоб- ное переименование в процессе распознавания становится не- нужным. Собственно говоря, три различных имени — (ехр\ (term) и (factor) — были включены в грамматику только как средства описания отношения предшествования между операто- рами (например, для указания того, что умножение следует выполнять прежде сложения). Поскольку эта информация со- держится в нашей матрице предшествования, то становится не-' нужным различать эти три имени в процессе грамматического разбора. Хотя мы проиллюстрировали метод операторного предше- ствования только на разборе отдельных предложений, этот ме- тод применим и для всей программы в целом. Вы можете попробовать применить этот метод к программе на рис. 5.1, ис- пользуя матрицу предшествования на рис. 5.7. Результат дол- жен совпадать с деревом грамматического разбора, изображен- ном на рис. 5.4, за исключением различий в именах нетерми- нальных символов, подобных только что рассмотренным. Другой метод грамматического разбора, который мы рас- смотрим в этом разделе, это нисходящий метод, называемый
5.1. Основные функции компилятора 243 рекурсивным спуском. Процессор грамматического разбора, ос- нованный на этом методе, состоит из отдельных процедур для каждого нетерминального символа, определенного в граммати- ке. Каждая такая процедура старается во входном потоке най- ти подстроку, начинающуюся с текущей лексемы, которая мо- жет быть интерпретирована как нетерминальный символ, свя- занный с данной процедурой. В процессе своей работы она может вызывать другие подобные процедуры или даже рекур- сивно саму себя для поиска других нетерминальных символов. Если эта процедура находит соответствующий нетерминальный символ, то она заканчивает свою работу, передает в вызвавшую ее программу признак успешного завершения и устанавливает указатель текущей лексемы на первую лексему после распо- знанной подстроки. Если же процедура не может найти под- строку, которая могла бы быть интерпретирована как требуе- мый нетерминальный символ, она заканчивается с признаком неудачи или же вызывает процедуру выдачи диагностического сообщения и процедуру восстановления. Рассмотрим в качестве примера грамматическое правило 13 на рис. 5.2. Процедура метода рекурсивного спуска, соответ- ствующая нетерминальному символу <read>, прежде всего ис- следует две последовательные лексемы, сравнивая их с READ и (. В случае совпадения эта процедура вызывает другую про- цедуру, соответствующую нетерминальному символу <id — list>< Если процедура <id — list) завершится успешно, то процедура <read> сравнивает следующую лексему с ). Если все эти про- верки окажутся успешными, то процедура (read) завершается с признаком успеха и устанавливает указатель текущей лексемы на лексему, следующую за ). В противном случае процедура <read> завершается с признаком неудачи. Эта процедура лишь немного сложнее, чем те несколько определенных грамматикой альтернатив для соответствующих нетерминалов. Дополнительно процедура должна решать, ка- кую альтернативу попробовать в качестве следующей. Для ме- тода рекурсивного спуска необходимо, чтобы соответствующую альтернативу можно было выбрать на основании анализа оче- редной входной лексемы. Существуют другие нисходящие ме- тоды грамматического разбора, для которых это требование может не выполняться. Однако они не являются столь эффек- тивными, как метод рекурсивного спуска. Так, например, про- цедура, соответствующая <stmt>, анализирует очередную лексе- му для того, чтобы выбрать одну из четырех возможных аль- тернатив. Если это лексема READ, то вызывается процедура, соответствующая нетерминальному символу <read>; если это лексема id, то вызывается процедура, соответствующая нетер- минальному символу (assign), поскольку это единственная аль- тернатива, которая может начинаться с лексемы id, и т. д.
244 Гл. 5. Компиляторы Если мы попытаемся написать полный набор процедур для грамматики на рис. 5.2, мы столкнемся со следующей труд- ностью. Процедура для <id—list), соответствующая правилу 6, будет не в состоянии выбрать одну из двух альтернатив, по- скольку обе альтернативы id и <id — list) могут начинаться с лексемы id. Тут скрыта и более существенная трудность. Если процедура каким-либо образом решит попробовать вторую аль- тернативу (<id — list), id), то она немедленно вызовет рекурсив- но саму себя для поиска нетерминального символа <id — list). 1 (prog) PROGRAM (prog-name) VAR (dec-list) BEGIN (stmt-list) END# 2 (prog-name) :=» id За (dec-list) :** (dec) { 5 (dec) } 4 {dec) :« {id-list) : (type) 5 (type) :«• INTEGER ба (id-list) :» id { » id } 7а (stint-list) (stmt) { ; (stmt) } 8 (stmt) :» (assign) | (read) j (write) | (for) 9 (assign) :» Id (exp) Юа {exp> :*» (term) { 4* (term) | - (term) } На (term) :*=» (factor) { * (factor) | DIV (factor) } 12 {factor) :« 14 | int | ( (exp) ) 13 (read) :» READ ( (id-list) ) 14 (write) WRITE ( (id-list) } 15 (for) ;=» FOR (index-exp) DO (body) 16 (index-exp) :«» id (exp) TO (exp) 17 (body) :« (stmt) I BEGIN (stmt-list) END Рис. 5.9. Упрощенная грамматика Паскаля, модифицированная для грамма- тического разбсоя методом рекурсивного спуска. Это приведет еще к одному рекурсивному вызову и т. д., в ре- зультате чего образуется бесконечная цепочка рекурсивных вы- зовов. Причина этого заключается в том, что одна из альтер- натив для <id — list) начинается также с <id — list). Нисходя- щий грамматический разбор не применим непосредственно для грамматик, содержащих подобные левые рекурсии. Те же проблемы возникнут и в отношении правил 3, 7, 10 и 11. Ме- тоды исключения левой рекурсии из правил грамматики можно найти в работах Грис [1971] и Ахо [1977]. На рис. 5.9 изображена та же грамматика, что и на рис. 5.2, но с исключенной левой рекурсией. Рассмотрим, например, пра- вило 6а на рис. 5.9: (id — list) := id { , id } Эта нотация, являющаяся широко принятым расширением БНФ, означает, что конструкция, заключенная в фигурные скобки, может быть либо опущена, либо повторяться один или более число раз. Таким образом, правило 6а определяет нетерминаль- ный символ <id — list) как состоящий из единственной лексе- мы id или же из произвольного числа следующих друг за дру- гом лексем id, разделенных запятой. Это очевидно, эквивалент-
5.1. Основные функции компилятора 245 но правилу 6 на рис. 5.2. В соответствии с этим новым опре- делением процедура, соответствующая нетерминальному симво- лу (и — list), сначала ищет лексему id, а затем продолжает сканировать входной текст до тех пор, пока следующая пара лексем не совпадет с запятой и id. Такая запись устраняет проблему левой рекурсии, а также решает вопрос, какую из возможных альтернатив <id — list) пробовать первой. Аналогичные изменения сделаны в правилах За, 7а, 10а и Па на рис. 5.9. Вам надо сравнить эти правила с соответ- ствующими им правилами на рис. 5.2 для того, чтобы убедить- ся в понимании сделанных изменений. Обратите внимание, что грамматика осталась рекурсивной: <ехр> определено в терминах <term>, который в свою очередь определен в терминах (factor), а одна из альтернатив для нетерминального символа (factor) включает в себя (ехр). Это означает, что рекурсивные вызовы процедур грамматического разбора по-прежнему возможны. Од- нако непосредственная левая рекурсия устранена. Цепочка вы- зовов, начиная с (ехр) и далее процедур, связанных с (term), (factor) и опять (ехр), должна продвинуть указатель текущей лексемы из входного файла как минимум на одну лексему вперед. На рис. 5.10 изображен грамматический разбор методом рекурсивного спуска предложения READ в строке 9 на рис. 5.1 с использованием грамматики на рис. 5.9. На рис. 5.10а изо- бражены процедуры, соответствующие нетерминальным симво- лам (read) и (id—list), логика которых совпадает с приведен- ным выше описанием. При этом предполагается, что переменная TOKEN содержит тип следующей лексемы из входного потока (в рамках схемы кодирования, изображенной на рис. 5.5). Вам надо внимательно ознакомиться с этими процедурами, что- бы убедиться, что вы понимаете, каким образом они получены из исходной грамматики. Заметьте, что в процедуре IDLIST символ запятая (,), за которым не следует лексема id, рассматривается как ошибка и процедура заканчивается с признаком неудачи. Если же после- довательность лексем типа «id, id» является правильной кон- струкцией языка, то метод рекурсивного спуска не даст правиль- ных результатов. Для такой грамматики необходимо использо- вать более сложные методы грамматического разбора, которые должны допускать во время нисходящего разбора возврат по входной строке после обнаружения того факта, что за послед- ней запятой не следует лексема id. На рис. 5.106 графически представлен процесс грамматиче- ского разбора методом рекурсивного спуска для анализируемо- го предложения. На фрагменте (i) изображен вызов процедуры READ, которая обнаружила лексемы READ и ( во входном по- токе (это изображено штриховыми линиями). Во фрагменте
procedure READ begin FOUND й“ FALSE if TOKEN ~ 8 {READ! then begin перейти к следующей лексеме if TOKEN = 20 ( ( } then begin перейти к следующей лексеме if IDLIST закончилась успешно then if TOKEN * 21 { ) J then begin FOUND г* TRUE перейти к следующей лексеме end {if ) 3 end Cif ( 3 , end Cif READ) if FOUND - TRUE then успешное завершение else неудачное завершение end {READ) procedure.IDLIST begin FOUND :* FALSE if TOKEN - 22 Cid) then begirt FOUND S- TRUE перейти к следующей лексеме while (TOKEN =14 {,)) and (FOUND « TRUE) da begin перейти к следующей лексеме if TOKEN = 22 {id) then перейти к следующей лексеме else FOUND s* FALSE end {while! end <if id) if FOUND « TRUE then успешное завершение else неудачное завершение end (IDLIST! Рис. 5.10, Грамматический разбор предложения READ методом рекурсивного (Спуска,
5.1. Основные функции компилятора 247 ;'(п) процедура READ вызвала процедуру IDLIST (изображено штриховой линией), которая обработала лексему id. Во фраг- менте (iii) процедура IDLIST закончила свою работу, передала управление процедуре READ с признаком успешного заверше- ния; процедура READ обработала входную лексему ). На этом анализ исходного предложения завершен. Процедура READ закончит свою работу с признаком успешного завершения, что означает, что нетерминальный символ (read} обнаружен. Об- ратите внимание, что последовательность вызова процедур и обработки лексем целиком определяется структурой предложе- ния READ. Фрагмент (iii) полностью совпадает с деревом грам- матического разбора на рис. 5.3а. Обратите внимание также на то, что дерево грамматического разбора строилось, начиная со своего корня, что и повлекло за собой термин нисходящий раз- бор. На рис. 5.11 представлен разбор методом рекурсивного спу- ска оператора присваивания в строке 14 на рис. 5.1. На рис. 5.11а изображены процедуры для нетерминальных симво- лов, необходимые для разбора этого предложения. Вы должны внимательно сравнить эти процедуры с соответствующими пра- вилами грамматики. На. рис. 5.116 изображены шаги граммати- ческого разбора (вызовы процедур и обработка лексем), ана- логичные приведенным на рис. 5.106. Читателю настоятельно рекомендуется проследить каждый шаг анализа этого предло- жения с использованием процедур рис. 5.11а. Сравните де- ревья грамматического разбора на рис. 5.116 и рис. 5.36. Обра- тите внимание, что различия между этими двумя деревьями в точности соответствуют различиям между грамматиками, изо- браженными на рис. 5.9 и рис. 5.2. Мы привели примеры грамматического разбора отдельных предложений методом рекурсивного спуска. Однако этот метод применим и ко всей программе в целом. В этом случае для осуществления синтаксического анализа следует просто обра- титься к процедуре, соответствующей нетерминальному символу <prog>. В результате работы этой процедуры будет построено дерево грамматического разбора для всей программы. Вы може- те написать недостающие процедуры для всех нетерминальных символов грамматики на рис. 5.9 и применить метод рекурсив- ного спуска к программе на рис. 5.1. В качестве результата вы должны получить дерево грамматического разбора, анало- гичное изображенному на рис. 5.4. Различия должны быть свя- заны только с теми модификациями, которые имеются в грам- матике на рис. 5.9. Обратите внимание, что в языке программирования отсут- ствуют какие бы то ни были особенности, заставляющие отдать Предпочтение одному из методов грамматического разбора. Мы использовали один восходящий и один нисходящий метод для
procedure ASSIGN begin FOUND := FALSE if TOKEN =22 Cid} then begin перейти к следующей лексему if TOKEN =15 {:=} then begin перейти к следующей явкеене if ЕХР завершилась успешно then FOUND := TRUE end {if ;=} end {if id) if FOUND * TRUE then успешное завершение else неудачное завершение end {ASSIGN) procedure EXP begin FOUND != FALSE if TERM завершилась успешно then begin FOUND r TRUE while ((TOKEN =16 {+} или (TOKEN * 17 (•})) and (FOUND = TRUE) do begin перейти к следующей лексеме if TERM завершилась неудачно th&) FOUND := FALSE end {while} end {if TERM} if FOUND = TRUE then успешное завершение else неудачное завершение end СЕХР} procedure TERM begin FOUND i= FALSE if FACTOR завершилась успешно then begin FOUND := TRUE while ((TOKEN =18 {*)) или (TOKEN = 19 CDIV))) and (FOUND * TRUE) do begin перейти к следующей лексеме if FACTOR закончилась неудачно then. FOUND ;= FALSE end {while} end {if FACTOR} if FOUND =TRUE then успешное завершение else неудачное завершение end (TERM}
FACTOR begin FOUND := FALSE if (TOKEN = 22 Cid)) или TOKEN =23 (inti) then begin FOUND := TRUE перейти к следующей лексеме end Cif id или int} else if TOKEN =20 С С } then begin перейти к следующей лексеме if ЕХР завершилась успешно then if TOKEN = 21 С ) } then begin FOUND s= TRUE перейти к следующей лексеме end Cif ) У end (if ( J if FOUND = TRUE then успешное завершение else неудачное завершение end (FACTOR} <£ idi {VARIANCE}
250 Гл. 5. Компиляторы id2 ini {SUMSQ} {100} Рис. 5.11. Грамматический разбор предложения присваивания методом ре- курсивного спуска. разбора одной и той же программы с использованием практи- чески одинаковой грамматики. Возможно также одновременное использование этих методов. Некоторые компиляторы использу- ют метод рекурсивного спуска для распознавания конструкций относительно высокого уровня (например, до уровня отдельных предложений языка), а потом переключаются на метод, ана- логичный методу операторного предшествования для анализа таких конструкций, как, например, арифметические выражения. Дальнейшее обсуждение методов грамматического разбора содержится в работах Ахо [1977] t Льюис [1976] и Грис [[1971].
5.1. Основные функции компилятора S51 5.1.4. Генерация кода После того как синтаксис программы проанализирован, по- следним шагом процесса компиляции является генерация объ- ектного кода. В этом разделе мы обсудим простой метод, кото- рый генерирует объектный код для каждого фрагмента про- граммы, как только распознан синтаксис этого фрагмента. Для реализации рассматриваемого метода генерации объ- ектного кода необходим набор подпрограмм, соответствующих каждому правилу и каждой альтернативе в правилах грамма- тики. Как только в результате процесса грамматического раз- бора будет распознан фрагмент текста исходной программы, соответствующий некоторому правилу грамматики, вызывается подпрограмма, соответствующая этому правилу. Эти программы часто называют семантическими программами, поскольку вы- полняемые ими действия связаны со смыслом, который мы свя- зываем с соответствующими конструкциями языка. В рамках нашей простой схемы эти семантические программы непосред- ственно генерируют объектный код. Поэтому мы будем назы- вать их программами генерации кода. В более сложных компи- ляторах подобные семантические программы могут генерировать некоторую промежуточную форму представления программы, пригодную для последующего анализа с целью генерации более эффективного объектного кода. Мы рассмотрим эту возмож- ность более детально в разд. 5.2 и 5.3. Программы генерации кода, которые мы обсудим в этом разделе, предназначены для использования с грамматикой на рис. 5.2. Как мы уже видели, ни один из методов грамматиче- ского разбора, обсуждавшихся в разд. 5.1.3, не распознает в точности те конструкции, которые описаны грамматикой. Метод операторного предшествования игнорирует некоторые нетерми- нальные символы, а метод рекурсивного спуска вынужден ис- пользовать несколько модифицированную грамматику. Сущест- вуют, однако, методы грамматического разбора ненамного слож- нее описанных, которые могут осуществлять грамматический разбор в строгом соответствии с грамматикой на рис. 5.2. Мы выбрали именно эту грамматику для обсуждения программ ге- нерации кода только для того, чтобы подчеркнуть, что методы генерации кода не связаны непосредственно с каким-либо кон- кретным методом грамматического разбора. Генерируемый код, очевидно, зависит от того, на каком компьютере будет выполняться компилируемая программа. В этом разделе мы будем рассматривать генерацию объектного кода для машины УУМ/ДС. Для своих рабочих данных наши программы генерации кода будут использовать две структуры: список и стек. Данные, попадающие в список, выбираются в том же порядке, в котором они туда записывались, т. е. в
252 Гл. 5. Компиляторы соответствии со стратегией «первым пришел — первым ушел». Данные, попадающие в стек, выбираются из него в обратном порядке, т. е. в соответствии со стратегией «первым пришел —• последним ушел». Переменная LISTCOUNT используется в ка- честве счетчика элементов, содержащихся в данный момент в списке. Программы генерации кода также используют специфи- каторы лексем, описанные в разд. 5.1.2 и обозначаемые S (лек- сема). Для лексемы id выражение S(id) является именем со- ответствующего идентификатора или указателем на него в таб- лице символов. Для лексемы int выражение S (int) является зна- чением соответствующего целого числа, например =Ц=100. Большинство программ, названных нами программами гене- рации объектного кода, в качестве результата своей работы действительно генерируют фрагменты объектного кода для ком- пилируемой программы. Мы символически запишем эти фраг- менты, используя язык ассемблера для УУМ. Вы должны по- мнить, однако, что в действительности генерируемый код чаще всего записывается на языке машинных команд, а не на языке ассемблера. Мы предполагаем, что после генерации каждого фрагмента объектного кода указатель свободной памяти LOCCTR модифицируется таким образом, чтобы он все время указывал на первую свободную ячейку компилируемой програм- мы (в точности так, как это происходит в ассемблере). Рис. 5.12 иллюстрирует процесс генерации объектного кода для предложения READ в строке 9 программы рис. 5.1. Для удобства дерево грамматического разбора этого предложения повторено на рис. 5.12а. Это дерево можно получить многими различными методами грамматического разбора. Однако неза- висимо от используемого метода на каждом шаге процесса грамматического разбора распознается самая левая подстрока входного текста, которая может быть интерпретирована в соот- ветствии с каким-либо из правил грамматики. В методе опера- торного предшествования подобное распознавание осуществля- ется, когда подстрока входного текста редуцируется к некото- рому нетерминальному символу <Ni>. В методе рекурсивного спуска распознавание происходит в тот момент, когда соответ- ствующая процедура заканчивает свою работу с признаком ус- пешного завершения. Таким образом, в процессе грамматиче- ского разбора всегда будет сначала распознан идентификатор VALUE в качестве нетерминального символа <id — list), а уже потом предложение в целом будет распознано в качестве нетер- минального символа <read>. На рис. 5.12в символически изображен объектный код, ко- торый должен быть сгенерирован для предложения READ. Этот код представляет собой вызов подпрограммы XREAD, ко- торая должна содержаться в стандартной библиотеке компиля- тора. Эта подпрограмма может быть вызвана также любой
5.1. Основные функции компилятора 253 программой, желающей выполнить операцию чтения. Подпро- грамма XREAD связывается с генерируемой программой связы- вающим загрузчиком или же редактором связей. (Компилятор должен включить в объектную программу достаточно инфор- мации для определения всех необходимых связей, используя, возможно, средства модификации, описанные в гл. 2.) Подоб- <id~list> s:= id добавить S<id) к списку увеличить LISTCOUNT на 1 <id-list> ::= <id-list) , id добавить S(id) к списку увеличить LISTCOUNT на 1 Cread> READ ( <id-list> ) сгенерировать C +JSUB XREAD! е внешние ссылки для XREAD сгенерировать С WORD LISTCOUNTJ Гог каждый элемент списка do begin удалить S(ITEM) из списка сгенерировать С WORD S(ITEM)J end LISTCOUNT s= 0 6. +JSUB XREAD WORD 1 WORD VALUE в Рис. 5.12. Генерация объектного кода для предложения READ. ная техника обычно используется при компиляции предложе- ний, выполняющих относительно сложные функции. Использо- вание подпрограмм в этом случае позволяет избежать повторной генерации больших фрагментов программы, что существенно сокращает генерируемую программу. Поскольку подпрограмма XREAD может быть использована для любых операций чтения, она должна иметь параметры, определяющие детали этой операции. В нашем случае список параметров подпрограммы XREAD помещается непосредственно за инструкцией JSUB, которая ее вызывает. Первое слово в этом списке параметров содержит величину, определяющую ко- личество переменных, которым должно быть присвоено значение в результате осуществления операции чтения. В последующих
254 Гл. 5. Компиляторы словах содержатся адреса этих переменных. Таким обра- зом, вторая строка на рис. 5.12в определяет, что должно быть прочитано значение одной переменной, а третья строка содер- жит ее адрес. Адрес первого слова списка параметров будет автоматически помещен в регистр L при выполнении инструк- ции JSUB. Подпрограмма XREAD может использовать этот адрес для определения своих параметров и, сложив содержимое регистра L с длиной списка параметров, определить значение адреса возврата. На рис. 5.126 представлен набор программ, которые могут использоваться для генерации объектного кода. Первые две программы соответствуют двум альтернативам для нетер- минального символа (id— list) в грамматическом правиле 6 на рис. 5.2. В любом случае спецификатор лексемы S (id) для очередного идентификатора, являющегося элементом <id — list), включается в список, используемый программами генерации кода. Соответствующая переменная LISTCOUNT увеличивается на единицу, чтобы отразить включение в список нового иденти- фикатора. После того как нетерминальный символ <id — list) будет разобран, список будет содержать спецификаторы лексем для всех идентификаторов, содержащихся в <id — list). Когда будет распознано предложение (read), эти спецификаторы лек- сем будут удалены из списка и использованы для генерации объектного кода, соответствующего операции чтения READ. Обратите внимание, что в процессе генерации дерева грам- матического разбора, изображенного на рис. 5.12а, вначале рас- познается (id — list), а уже потом (read). На каждом шаге грамматического разбора вызывается соответствующая програм- ма генерации объектного кода. Вы должны внимательно про- анализировать этот пример, чтобы убедиться в правильном по- нимании процесса генерации объектного кода, символически представленного на рис. 5.12в с помощью программ генерации на рис. 5.126. На рис. 5.13 изображен процесс генерации объектного кода для предложения присваивания в строке 14 на рис. 5.1. На рис. 5.13а приведено дерево грамматического разбора этого предложения. Основная работа при грамматическом разборе этого предложения состоит в анализе нетерминального символа <ехр) в правой части оператора присваивания. Как мы видим, в процессе грамматического разбора идентификатор SUMSQ распознается сначала как (factor), потом как (term). Далее распознается целое число 100 как (factor); затем фрагмент SUMSQ DIV 100 распознается как (term) и т. д. Эта цепочка шагов примерно совпадает с изображенной на рис. 5.86. Поря- док распознавания фрагментов этого предложения совпадает с порядком, в котором должны выполняться соответствующие вы- числения; сначала вычисляются подвыражения SUMSQ DIV 100
5.1. Основные функции компилятора 255 и MEAN*MEAN, а затем второй результат вычитается из первого. Как только очередной фрагмент предложения распознан, вы- зывается соответствующая программа генерации кода. Предпо- ложим, что мы хотим сгенерировать код, соответствующий пра- вилу (term)! ::= (term)2 * (factor) Индексы использованы здесь для различения двух вхождений нетерминального символа (term). Наши программы генерации кода выполняют все арифметические операции с использовани- ем регистра А, и мы заведомо должны сгенерировать в объект- ном коде операцию MUL. Результат этого умножения <term>i — после операции MUL сохранится в регистре А. Если либо <term>2, либо (factor) уже находятся на регистре А (после вы- полнения предыдущих вычислений), генерация инструкции MUL — это все, что нам нужно. Иначе мы должны сгенериро- вать также инструкцию LDA, предшествующую инструкции MUL. В этом случае нам также надо предварительно сохра- нить значение регистра А, если оно понадобится в будущем. Очевидно, необходимо отслеживать значение, помещенное в регистр А, после каждого фрагмента генерируемого объектного кода. Это можно осуществить за счет расширения понятия спецификатора лексемы на нетерминальные узлы дерева грам- матического разбора. В только что рассмотренном примере спецификатору узла S((term)i) будет присвоено значение гА, указывающее на то, что результат этих вычислений содержится в регистре А. Переменная REGA используется для указания на самый высокий уровень узла дерева грамматического разбора, значение которого помещено в регистр А в сгенерированном до данного момента объектном коде (т. е. указатель на узел, спе- цификатор которого равен гА). Очевидно, в каждый момент процесса генерации объектного кода существует ровно один такой узел. Если же в регистре А нет значения, соответствую- щего некоторому узлу, то спецификатор этого узла аналогичен спецификатору лексемы: это либо указатель на переменную (в таблице символов), содержащую соответствующее значение, либо указатель на целую константу. Чтобы проиллюстрировать приведенные соображения, рас- смотрим программу генерации объектного кода на рис. 5.136, соответствующую правилу (term)! ::= (term)2 * (factor) Если спецификатор узла какого-либо из операндов равен гА, то соответствующее значение уже содержится в регистре А и программа генерирует только одну инструкцию MUL. Адрес операнда для инструкции MUL содержится в спецификаторе
(assign) id {VARIANCE} (assign > <pxd> ::= <2Xp>l U <exp>l (exp) a. s:= id <exp> GETA (<exp>) сгенерировать C STA S(id)3 REGA s- null (term) S(<exp>) s= S(<terM>) if S(<exp)> = гA then REGA s= <exp> (exp>2 + (ter«> if S(<exp>2) « rA then сгенерировать C ADD S((ter«>)3 else if S(<tere>) = rA then сгенерировать C ADD S((exp>2)3 else begin GETA ((exps>2) сгенерировать C ADD S((terw>)3 t end Sf(exp>l> s= rA *EGA s« <exp>l -Ap?2 * (tern» If S(<exp>2) e rA then сгенерировать C SUB S((ter«>)1 else begin GETA ((exp>2) сгенерировать C SUB S(<terrt>)J j end S(<exp>l) := rA REGA ;== <exp)l
(tare) :s« (factor) S(<term>J 8» S((fdttor» if S(<term)> * rA then REG A :« (ter«> (ter»>l (ter«)2 * (factor) if S((terw>2) ~ rA then сгенерировать C MUL S((faCtor))l else if S(( factor» ® rA then сгенерировать C HUL S((tertt)2>l else begin GETA (<ter«>2) сгенерировать C MLB- S «factor» J end S((terw)l) s- rA REGA s=* (terw)l (tera>l <terw>2 DIV (factor) if S«terw>2> « rA then сгенерировать C DIV S«factor»7 else begin GETA <(ter«>2> сгенерировать E DIV S«factor))l end S((ter«>l) i« rA REGA i* (tere>l (factor) u« id S« factor)) ;= S(id> (factor) u*> int 8«factor» i* S(int> {factor) G- ( (exfi) j S((factor)) :« S((exp)> procedure GETA (NODE) begin if REGA = null then сгенерировать C LDA S(N0DE>2 else if S(NODE) « rA then begin породить новые рабочие переменные Ti сгенерировать С STA Til ссылки вперед на Ti S(REGA) Ti .сгенерировать C LDA. SXNQDEJT end (if rAi S(NODE) ;=« rA REGA 8- NODE end (GETA) ? / 9 Зак. 792
258 Гл. 5. Компиляторы LDA SUMSQ JDIV STA Tl LDA MEAN MUL MEAN STA T2 LDA Tl SUB T2 STA VARIANCE Z - Рис. 5.13. Генерация объектного кода для предложения присваивания. узла другого операнда (значение которого не находится на ре- гистре А). В противном случае вызывается процедура GETA« Эта процедура, изображенная на рис. 5.1 Зв, генерирует инст- рукцию LDA для загрузки значения, связанного со значением <term>2, в регистр А. Дополнительно перед инструкцией LDA процедура генерирует инструкцию STA для сохранения текуще- го значения регистра А, если только REGA не нуль (равенство REGA нулю означает, что это значение больше не понадобит- ся). Это значение запоминается в некоторой рабочей перемен- ной. Рабочие переменные образуются в процессе генерации объектного кода (с именами Tl, Т2, ...) по мере необходимо- сти. Для используемых компилятором рабочих переменных бу- дет отведено место в конце объектной программы. На узел дерева грамматического разбора, связанный со значением, содер- жащимся в регистре А, указывает переменная REGA. Специфи- катор этого узла модифицируется, чтобы указывать на рабочую переменную, используемую для хранения этого значения. После того как все необходимые инструкции сгенерированы, программа генерации кода устанавливает спецификатор S (<term>i) и переменную REGA таким образом, чтобы было вид- но, что значение, соответствующее <termi>, находится в настоя- щее время в регистре А. На этом процедура генерации кода для операции * завершается. Программа генерации кода, соответствующая операции +, практически совпадает с только что рассмотренной програм- мой для операции *. Программы для DIV и — также анало- гичны, за исключением того, что для этих операций необходимо, чтобы на регистре А был установлен именно первый операнд. Программа генерации кода для операции присваивания <assing> состоит в загрузке присваиваемой величины на регистр А (с использованием GETA) и генерации инструкции ST А. Обратите внимание, что переменная REGA потом обнуляется, поскольку код, соответствующий предложению присваивания, полностью сгенерирован и все промежуточные результаты больше не нужны. Оставшиеся правила, показанные на рис. 5.136, не требуют генерации каких-либо машинных инструкций, поскольку они не
F 1 Основные функции компилятора 259 соответствуют никаким вычислениям или перемещениям дан- ных. Программы генерации кодов для этих правил должны со- ответствующим образом установить спецификатор для узла са- мого верхнего уровня, чтобы он указывал на ячейку, содержа- щую соответствующее значение. На рис. 5.136 представлено символическое изображение объектного кода, сгенерированного для предложения присваива- ния. Вы должны внимательно проследить генерацию этого кода, чтобы понять механизм работы программ на рис. 5.136 и 5.1 Зв. Вам надо также проверить, что эти коды действительно выпол- няют те вычисления, которые записаны в предложении исход- ной программы. На рис. 5.14 изображены другие программы генерации кода для грамматики рис. 5.2. Программа для (prog — name) генери- рует заголовок объектной программы, аналогичной результату работы директив ассемблера START и EXTREF. Она также генерирует инструкции для сохранения адреса возврата и пе- рехода на первую выполняемую инструкцию компилируемой программы. После того как вся программа распознана, резер- вируется память для рабочих переменных (Ti). Затем все ссыл- ки на эти переменные фиксируются в объектном коде с исполь- зованием процедуры обработки ссылок вперед, осуществляемой однопросмотровым ассемблером. Компилятор генерирует так- же модифицирующие записи, необходимые для описания внеш- них ссылок на библиотечные подпрограммы. Генерация кода для предложения (for) состоит из не- скольких шагов. После того как распознан нетерминальный символ (index — exp), генерируется код для инициализации ин- дексной переменной цикла и инструкции проверки конца цикла. Часть информации также записывается в стек для дальнейше- го использования. Далее независимо генерируются коды для каждого предложения, составляющего тело цикла. После того как вся конструкция (for) распознана, генерируется код, уве- личивающий индексную переменную и осуществляющий воз- врат на начало цикла для проверки условий его завершения. Здесь программы генерации кода используют информацию, за- писанную в стек программой, соответствующей (index — exp). Использование стека для запоминания этой информации позво- ляет обрабатывать вложенные циклы (for). Вы можете проследить весь процесс генерации кода для этой программы, используя дерево грамматического разбора рис. 5.4 в качестве управляющей информации. Результат дол- жен совпасть с изображенным на рис. 5.15. Еще раз повторим, что это всего лишь символическое изображение сгенерирован- ного кода. Большинство компиляторов генерирует коды непо- средственно на машинном языке. 9*
260 Гл 5. Компиляторы <pros> 5PROGRAM <ргод-паме> VAR (dec-list) BEGIN (stmt-list) END сгенерировать C 1DL RETADR3 сгенерировать C RSUB3 for каждая используемая переменная Ti do сгенерировать (Ti RESW 13 Вставить C J EXADDR3 (переход на первое выполняемое предложение} в байты 3-5 объектной программы сформировать ссылки вперед на переменные Ti сгенерировать запись-модификатор для внешних ссылок сгенерировать Г END3 <рГ09-ПМ»е> 8 555 сгенерировать С START 03 сгенерировать С EXTREF XREAD,XURITE1 сгенерировать С STL RETADR3 увеличить LOCCTR на 3 (место для команды перехода на первое выполняемое предложение} сгенерировать (RETADR RESW 13 <dec-liei> ?s= (любые альтернативы} сохранить LOCCTR в качестве EXADDR (адрес первого выполняемого предложения} (dec) :s“ (id-list) : (type) for каждый элемент списка do begin исключить S(NAME) из списка занести LOCCTR в таблицу символов в качестве адреса NAME сгенерировать CS(NAME) RESW 13 end LISTCOUNT := 0 (type} 55= INTEGER (не нужны никакие действия по генерации кода} (stmt-list) (любые альтернативы} (не нужны никакие действия по генерации кода;
5.1. Основные функции компилятора 261 <&Ш> {любые альтернативы? Сне нужны никакие действия по генерации кода? {write) WRITE «id-list» сгенерировать С +JSUB XWRITE1 внешние ссылки для XWRITE сгенерировать С UORB LISTCQUNT3 for каждый элемент списка do begin исключить S(ITEM> из списка сгенерировать С WORD S(!TEM)3 end LISTCOUNT 0 {for? ss« FOR (index-exp? JOO {body? взять из стека JUHFADDR {адрес команды выхода из Ц^ЛЦ? взять из стека S(INDEX) {индексная переменная} взять из стека LOOPAPPR {адрес начала цикла? сгенерировать С LDA S(INDEX)} сгенерировать С ADD #11 сгенерировать Г J LOOPADDRJ вставить £ JGT LOCCTR? в ячейку JUHFADDR (index-exp? s;= id 5= <ехр?1 Т0(ехр?2 BETA «ехр>1> поместить в стек LOCCTR {адрес начала цикла? поместить в стек S(id) {индексная переменная} сгенерировать С STA S(id)3 сгенерировать С COMP S((exp>2>3 поместить в стек LOCCTR {адрес выхода ив цикла? увеличить LOCCTR на 3 {место для команды перехода} REGA a* null (body? ss« (любые альтернативы? (не нужны никакие действия по генерации кода? рис. 5.14. Другие программы генерации объектного кода для грамматики на рис, 5.2.
262 Гл. 5. Компиляторы Строка Символическое представление сгенерированного кода 1 STATS START 9 {заголовок программы) EXTREF XREAD,XWRITE STL RETADR {сохранение адреса возврата! J , {EXADDR! RETADR REStf 1 3 SUM RESH 1 SUMSQ RESH 1 I RESH 1 VALUE RESH 1 MEAN RESH Д VARIANCE RESH 1 S {EXADDR! RESH 40 {SUM :« 0! STA SUM 6 LDA *0 {SUMSQ := 0) STA SUMSQ 7 LDA {FOR I s= i TO 100) COMP 4100 JGT <L2J CL1) STA I 7 +JSUB XREAD {READ(VALUE)} WORD 1 WORD VALUE 10 LDA SUM CSUM := SIX + VALUE! ADD VALUE STA SUM 11 LDA VALUE CSUMSQ j* SUMSQ + VALUE * VALUE! MUL VALUE ADD SUMSQ STA SUMSQ LDA 1 {конец цикла FORT ADD Si J <L1> 13 (L2) LDA SUM {MEAN s= SUM DIV 100} DIV 4100 STA MEAN 14 LDA SUMSQ {VARIANCE:=SUMSQ DIV 100~MEAN«MEAN! DIV £100 STA Tl LDA MEAN MIL MEAN STA T2 LDA Tl SUD T2 STA VARIANCE 15 +JSUL xwrite {WRITECMEAN,VARIANCE)) WORD 2 WORD MEAN WORD VARIANCE LDL RETADR {возврат) Ti RESW 1 {используемые рабочие переменные! T2 RESH 1 END Рис. 5.15. Символическое представление объектного кодаг сгенерированного для программы на рис, 5.1,
5.2. Машинно-зависимые особенности компиляторов _ 263 5.2. Машинно-зависимые особенности компиляторов В этом разделе мы кратко обсудим некоторые машинно-за- висимые расширения базовой схемы компиляции, представлен- ной в разд. 5.1. Целью компилятора является трансляция про- граммы, написанной на языке программирования высокого уров- ня, в машинные коды для некоторого компьютера. Большинство языков программирования высокого уровня построено так, что- бы быть относительно независимыми от тех машин, на кото- рых они будут использоваться (хотя достигнутая степень такой независимости существенно различна для разных языков). Это означает, что процесс анализа синтаксиса программ, написан- ных на этих языках, должен быть также машинно-независимым. Таким образом, неудивительно, что машинно-зависимые осо- бенности компилятора связаны с программами генерации и оптимизации объектного кода. На элементарном уровне можно сказать, что весь процесс генерации кода является машинно-зависимым, поскольку мы должны знать систему команд компьютера, для которого этот код генерируется. Существуют, однако, более сложные пробле- мы, вытекающие из машинной зависимости, которые возникают при распределении регистров и перестановки машинных инст- рукций для повышения эффективности. Такого рода оптимиза- ция кода обычно осуществляется с использованием промежуточ- ной формы представления компилируемой программы. В этой промежуточной форме синтаксис и семантика исходных пред- ложений программы уже полностью проанализированы, но трансляция в машинные коды еще не осуществлена. Анализи- ровать и модернизировать программу, представленную в такой промежуточной форме для оптимизации кода, существенно лег- че, чем для исходной формы программы на языке высокого уровня или для этой программы, представленной в машинных кодах. В разд. 5.2.1 мы приведем одну стандартную форму пред- ставления программы в промежуточном виде. Она не сильно зависит от той машины, для которой разрабатывается компи- лятор. Однако использование этой формы необходимо для об- суждения машинно-зависимой оптимизации кода в разд. 5.2.2. Существует также много машинно-независимых методов опти- мизации кода, которые используют аналогичное промежуточное представление программы. Некоторые из этих методов мы об- судим в разд. 5.3.
’ 264 Гл. 5. Компиляторы 5.2.1. Промежуточная форма представления программы Существует много различных способов представления про- граммы в некоторой промежуточной форме для анализа и Оптимизации кода. См., например, Ахо [1977] и Грис [1971]. Метод, который мы обсудим в этом разделе, состоит в пред- ставлении выполняемых инструкций программы с помощью по- следовательности четверок {quadruples). Каждая четверка за- писывается в виде операция, opl, ор2, результат где операция — это выполняемая объектным кодом функция, 6р1 и ор2 — операнды этой операции, а «результат» определяет, куда должно быть помещено результирующее значение. Например, предложение исходной программы SUM := SUM + VALUE может быть представлено четверками следующим образом: + , SUM, VALUE, iI :=, i, , , SUM Здесь ii обозначает промежуточный результат (SUM VALUE); вторая четверка присваивает это значение перемен- ной SUM. Для обеспечения дополнительных возможностей оп- тимизации кода присваивание рассматривается как независи- мая операция (:==). Аналогично предложение VARIANCE := SUMSQ DIV 100 - MEAN*MEAN может быть представлено в виде последовательности четверок следующим образом: DIV, SUMSQ, #100, i, * , MEAN , MEAN, i2 , h > i2 , is l- , i3 , , VARIANCE Эти четверки могут быть сгенерированы программами гене- рации промежуточного представления (аналогичными тем, ко- торые были рассмотрены в разд. 5.1.4). Для оптимизации кода эти четверки могут анализироваться и модернизироваться мно- гими различными способами. Например, можно изменить поря- док следования четверок для исключения ненужных операций запоминания и загрузки регистров. Промежуточные результаты ij можно запомнить в регистрах или рабочих переменных, с дем чтобы обеспечить их эффективное использование. Мы об-
5.2. Машинно зависимые особенности компиляторов 265 рперацид Opf Op 2 Результат (1) #0 SUM {SUH S’ 0} (2) -ж #0 SUMSQ {SUMSQ S’ 0} СЗ) I {FOR I S’ 1 TO 100}. (4) JGT I i 100 (15) (5) CALL XREAD {READ(VALUE)} (6) РАКАМ VALUE <7) + SUM VALVE {SUH ! SUM + VALUE} <8) SUM (9) * VALUE VALUE (SUMSQ :• SUMSQ + VALUE * VALUE} ,(Ю) + SUMSQ *2 *3 (И) *3 SUMSQ (12) * I 4 UWffltilW» FOR} (13) I (19) J <4) (15) DIV SUM <100 *5 {HEAD 1" SUM OtV lOt)} (16) :* *5 MEAH (17) DIV SUMSQ <100 4 {VARIANCE 1- SUMSQ DIV 100 (18) * MEAN MEAN 4 MEAN * MEAN} (19) - 4 4 (20) (21) CALL XWRITE (WRITE(MEAN,VARIANCE)} (22) PARAM MEAN • (23) PARAM VARIANCE Рис. 5.16. Промежуточная форма представления программы на рис. 5.1. судим некоторые из этих возможностей в последующих разде- лах. После осуществления оптимизации на уровйе четверок мо- дифицированная последовательность четверок должна транс- лироваться в машинные коды. Обратите внимание, что четверки расположены в том поряд- ке, в котором должны выполняться соответствующие инструк- ции объектного кода, что существенно облегчает анализ для оптимизации кода. Это означает также, что трансляция в ма- шинные инструкции будет относительно простой. На рис. 5.16 изображена последовательность четверок, соот- ветствующая исходной программе на рис. 5.1. Обратите внима- ние, что предложения READ и WRITE представлены операцией CALL, за которой следует четверка PARAM, определяющая
266 Гл. 5. Компиляторы параметры операций READ и WRITE. Четверка PARAM будет, разумеется, при окончательной генерации машинного кода от- транслирована в список параметров, подобный изображенному на рис. 5.15. Операция JGT в четверке (4) сравнивает значе- ние двух своих операндов и осуществляет переход к четвер- ке (15), если первый операнд больше второго. Операция J в четверке (14) осуществляет безусловный переход к четвер- ке (4). Для того чтобы увидеть соответствие между исходной про- граммой и последовательностью четверок, вам надо сравнить рис. 5.16 и 5.1. Вы можете также сравнить последовательность четверок на рис. 5.16 с изображением объектного кода на рис. 5.15. 5.2.2. Машинно-зависимая оптимизация кода В этом разделе мы кратко опишем несколько различных путей осуществления машинно-зависимой оптимизации кода. Более подробная информация о методах оптимизации содержит- ся во многих книгах, посвященных компиляторам, таких как Ахо [1977] и Грис [1971]. Первая проблема, которую мы обсудим, состоит в назначе- нии и распределении регистров. Многие компьютеры имеют несколько регистров общего назначения, которые могут исполь- зоваться для хранения констант, значений переменных, проме- жуточных результатов и т. д. Те же регистры часто могут ис- пользоваться и для адресации (в качестве базовых или индекс- ных регистров). Мы, однако, рассмотрим лишь использование регистров в качестве операндов соответствующих инструкций. Машинные инструкции, которые используют регистры в ка- честве своих операндов, обычно выполняются быстрее, чем со- ответствующие инструкции, требующие обращения к памяти. Следовательно, мы должны стараться разместить на регистрах все переменные и промежуточные результаты, которые потом будут использоваться в программе. При каждом обращении к памяти или вычислении промежуточного результата это значе- ние можно запомнить в каком-либо регистре; тогда оно будет доступно для последующего использования уже без обращения к памяти. Мы применили очень простую версию этого метода в разд. 5.1.4, когда отслеживали значение, находящееся в дан- ный момент на регистре А. Рассмотрим в качестве примера последовательность четве- рок на рис. 5.16. Переменная VALUE используется один раз в четверке 7 и дважды в четверке 9. Если бы у нас было до- статочно регистров, то можно было бы обратиться за этой ве- личиной к памяти только один раз. Ее значение осталось бы на регистре для использования в командах, соответствующих
5.2. Машинно зависимые особенности компиляторов 267 четверке 9. Аналогично четверка 16 запоминает значение г5 в переменной MEAN. Если сохранить f5 на регистре, то значение этой переменной может быть использовано, когда в четверке 18 потребуется значение переменной MEAN. Использование регистров может сделать также ненужным большинство рабочих переменных. Рассмотрим, например, машинный код на рис. 5.15, в котором для обработки шести из восьми промежу- точных результатов (i7) на рис. 5.16 достаточно воспользовать- ся только одним регистром (регистром А). В действительности, конечно же, редко бывает доступно столько регистров, сколько бы нам хотелось. Проблема, таким образом, сводится к выбору регистра, переменная в котором должна быть заменена при необходимости использовать этот регистр для какой-либо другой цели. Один из возможных под- ходов состоит в просмотре программы вперед, чтобы опреде- лить, когда каждый из регистров будет повторно использовать- ся. Переменная, которая не потребуется в течении наибольшего времени, как раз и определит искомый регистр. Если регистр, на который нужно записать новую информацию, содержит зна- чение некоторой переменной, уже находящейся в памяти, то старое значение этого регистра может быть просто затерто. В противном случае его значение должно быть сохранено в одной из рабочих переменных. Это одна из функций, осуще- ствляемая рассмотренной в разд. 5.1.4 процедурой GETA, кото- рая делает это с помощью рабочих переменных Ti. При распределении регистров компилятор должен также учи- тывать операторы передачи управления. Например, четверка 1 на рис. 5.16 присваивает переменной SUM значение 0. Это зна- чение можно было бы сохранить на некотором регистре для дальнейшего использования. Когда переменная SUM повторно используется в качестве операнда в четверке 7, можно было бы взять это значение непосредственно из регистра. Однако подоб- ное рассуждение может оказаться неправильным. Операция J в четверке 14 передает управление четверке 4. Если управление передастся четверке 7 именно таким образом, то значение пере- менной SUM окажется не занесенным на регистр. Это произой- дет, например, если на этот регистр будет занесено значение й в четверке 12. Таким образом, наличие инструкций перехода порождает определенные трудности контроля за состоянием ре- гистров. Один из способов решения этой проблемы состоит в разбие- нии программы на линейные участки. Линейным участком явля- ется последовательность четверок с одной точкой входа, нахо- дящейся в начале участка, и одной точкой выхода в конце линейного участка без каких-либо переходов внутри него. Дру- гими словами, каждая четверка, на которую может быть пере- дано управление в результате операции перехода, или же
268 Гл. 5. Компиляторы Рис. 5.17. Линейные участки и блок-схема для последователь- ности четверок на рис. 5.16. четверка, непосредственно следующая за четверкой передачи управления, начинает новый линейный участок. Поскольку вы- зов процедуры может оказать непредсказуемое влияние на со- держание регистров, операция CALL обычно также рассматри- вается как начало линейного участка. Распределение и исполь- зование регистров внутри линейного участка могут быть осу- ществлены только что описанным методом. Однако, когда управление передается от одного линейного участка к другому, все находящиеся на регистрах значения упрятываются в рабочие переменные. На рис. 5.17 изображено разбиение четверок рис. 5.16 на линейные участки. Участок А содержит четверки 1—3; уча- сток В содержит четверку 4 и т. д. Рис. 5.17 можно рассматривать также как струк- туру операторов передачи управления программы: стрелка от участка X к уча- стку Y обозначает, что управление пере- дается непосредственно от последней чет- верки участка X к первой четверке участ- ка Y. (Внутри линейного участка управле- ние, разумеется, передается последователь- но от одной четверки к другой в порядке их расположения.) Такого рода схема на- зывается блок-схемой программы. В рам- ках более сложных методов оптимизации кода блок-схема может стать объектом са- мостоятельного анализа с целью осуществления распределения регистров, сохраняющегося при переходе от одного линейного участка к другому. Другая возможность оптимизации кода состоит в переупо- рядочивании четверок перед преобразованием их в машинный код. Рассмотрим, например, четверки на рис. 5.18а. Они в основ- ном совпадают с четверками 17—20 на рис. 5.16 и соответст- вуют предложению 14 исходной программы на рис. 5.1. На рис. 5.18а изображен типичный машинный код, сгенерирован- ный по этим четверкам с использованием одного регистра (ре- гистра А). Этот код совпадает с кодом, изображенным на рис. 5.15 для предложения 14. Обратите внимание, что сначала вычисляется промежуточ- ное значение о и запоминается в рабочей переменной Т1. Да- лее вычисляется значение г2. В третьей четверке этой последо- вательности вычитается значение /2 из значения Л. Поскольку значение /2 только что вычислено, оно находится на регистре А. Однако это никак не удается использовать, поскольку для опе- рации вычитания на регистре должен находиться первый опе- ранд, а не второй. Следовательно, необходимо запомнить зна-
5.2. Машинно-Зависимые особенности компиляторов 269 чение /г в другой рабочей переменной (Т2) и перед вычитанием загрузить значение переменной и из рабочей переменной Т1 на регистр А. На основании несложного анализа оптимизирующий компи- лятор может распознать подобную ситуацию и переставить чет- верки таким образом, чтобы второй операнд операции вы- t>iv sumsq #юо q читания вычислялся перед пер- * вым. Такая перестановка изо- бражена на рис. 5.186. Первые две четверки последователь- ности поменялись местами В результате машинный код будет содержать на две ин- струкции меньше и будет ис- пользоваться только одна ра- бочая переменная вместо двух. Подобные соображения могут применяться для перестановки четверок, вычисляющих опе- ранды операции деления или любой другой операции для машинной инструкции, тре- * бующей строго определенного div порядка операндов. Другая возможность ма- шинно-зависимой оптимизации *•* MEAN WEAK i2 *1 *2 *3 i3 VARIANCE LDA SUMSQ DIV #100 STA TI LDA MEAN MUL MEAN STA T2 LDA TI SUB T2 STA VARIANCB MEAN MEAN ч SUMSQ #100 ч Ч 12 *3 ч VARIANCB кода состоит в использовании специфических характеристик и инструкций, имеющихся на вычислительной машине. На- пример, на ней могут суще- ствовать специальные инструк- ции для организации циклов или способы адресации, позво- ляющие сгенерировать более эффективный объектный код. Компьютеры серии VAX со- I LDA MEAN MUL MEAN STA TI IDA SUMSQ DIV #100 SUB TI STA* VARIANCE d Рис. 5.18. Переупорядочивание чет- верок для оптимизации кода. держат машинные инструкции высокого уровня, позволяющие выполнять такие сложные функции, как вызов процедуры и преобразование структур данных. Очевидно, что использование подобных особенностей, если это удается сделать, может суще- ственно повысить эффективность выполнения объектной про- граммы. Некоторые машины, к ним, в частности, относятся опреде- ленные модели компьютеров серии CYBER, имеют центральный процессор, состоящий из нескольких функциональных устройств.
Гл. 5. Компиляторы Для подобных компьютеров порядок расположения машинных инструкций может повлиять на скорость выполнения програм- мы. Последовательно расположенные инструкции, предназна- ченные для различных функциональных устройств, могут вы- полняться в некоторых случаях одновременно. Для того чтобы воспользоваться этой особенностью, оптимизирующий компиля- тор для подобных машин может переставить машинные инст- рукции объектного кода. Примеры и ссылки можно найти в Грис [Г971]. 5.3. Машинно-независимые особенности компиляторов В этом разделе мы кратко опишем некоторые особенности компиляторов, которые в большой мере не зависят от того, для какой конкретной машины они написаны. Как и в предыдущем разделе, мы не пытаемся описать все детали, связанные с ре- ализацией этих особенностей. Их можно найти в цитированной литературе. При описании устройства компилятора в разд. 5.1 мы имели дело только с простыми переменными, статически распределенными по ячейкам памяти объектной программы. В разд. 5.3.1 представлены некоторые альтернативные пути распределения памяти для компилируемой программы. В разд. 5.3.2 описываются методы работы со структурирован- ными переменными, такими как, например, массивы. В разд. 5.3.3 продолжается обсуждение методов оптимиза- ции кода, начатое в разд. 5.2.2. При этом мы сосредоточимся на машинно-независимых методах оптимизации машинного кода. В разд. 5.3.4 обсуждаются проблемы, связанные с ком- пиляцией блочно-структурированных языков программирова- ния, и предлагаются некоторые пути решения этих проблем. 5.3.1. Распределение памяти В схеме компиляции, предложенной в разд. 5.1 для всех пе- ременных, определенных программистом, при обработке соот- ветствующих декларативных операторов выделяются ячейки па- мяти в объектной программе. Рабочим переменным, включая такие, которые используются для хранения адреса возврата, также присваиваются фиксированные адреса внутри програм- мы. Этот простой метод распределения памяти обычно называ- ется статическим. Он часто используется для языков, подоб- ных Фортрану, не допускающих рекурсивного вызова процедур или подпрограмм и не предоставляющих средств динамического распределения памяти во время выполнения программы.
5.3. Машинно-независимые особенности компиляторов 271 Если же процедуры могут вызываться рекурсивно, как в Паскале, статическое распределение памяти не применимо. Рас* смотрим, например, рис. 5.19. На рис. 5.19а программа MAIN вызывается операционной системой или загрузчиком (вызов первого уровня). Первое, что делает программа MAIN,— запо- минает свой адрес возврата, находящийся на регистре L, в фиксированную ячейку RETADR внутри программы MAIN. На рис. 5.196 программа MAIN вызывает процедуру SUB (вызов 6 Рис. 5.19. Рекурсивный вызов процедуры в случае использования статиче- ского распределения памяти. второго уровня). Адрес возврата из этого вызова запоминается в фиксированной ячейке внутри процедуры SUB. Если теперь процедура SUB вызовет рекурсивно саму себя, как это изобра- жено на рис. 5.19в, возникнет следующая проблема. Процедура SUB запомнит адрес возврата для вызова третьего уровня в ячейке RETADR из регистра L. Это действие приведет к уни- чтожению адреса возврата для вызова второго уровня. В ре-» зультате окажется невозможным корректный возврат в про- грамму MAIN. Аналогичные трудности возникают с любыми другими пере- менными, используемыми процедурой SUB. При рекурсивных вызовах внутренним переменным процедуры SUB могут быть присвоены новые значения, которые уничтожают предыдущие значения этих переменных, хотя они могут понадобиться при вызове процедуры SUB второго уровня после возврата из ре- курсивных вызовов. Этот же вопрос рассматривался в разд. 4.3.1 в связи с обсуждением рекурсивной макрогенерации.
Стек Стек
5.3. Машинно-независимые особенности компиляторов 273 Очевидно, что при выполнении рекурсивных вызовов необ- ходимо сохранять предыдущие значения любых переменных, используемых в процедуре SUB, включая параметры, рабочие переменные, адреса возвратов, ячейки для запоминания регист- ров и т. д. Обычно это осуществляется с использованием дина- мического распределения памяти. Каждый вызов процедуры приводит к образованию области инициализации, содержащей память для всех используемых в процедуре переменных. Если процедура вызывается рекурсивно, каждый раз образовывается новая область инициализации. Каждая область инициализа- ции связывается с определенным уровнем вызова процедуры, а не с самой процедурой. Область инициализации не удаляется, до тех пор, пока не произойдет возврата из вызова соответ- ствующего уровня. Начальный адрес текущей области инициа- лизации обычно находится на базовом регистре, используе- мом процедурой для адресации своих переменных. В этом случае все значения переменных, используемые на разных уровнях вызова процедуры, хранятся независимо друг от друга. Обычно области инициализации располагаются в стеке, при- чем текущая область инициализации — в вершине стека. Это изображено на рис. 5.20. На рис. 5.20а, соответствующем рис. 5.19а, вызывается процедура MAIN; ее область инициали- зации записывается в стек. На базовый регистр В устанавли- вается начальный адрес этой текущей области инициализации. Первое слово области инициализации содержит обычно указа- тель PREV на предыдущую область инициализации в стеке. Поскольку эта область является первой, указатель имеет нуле- вое значение. Следующее слово области инициализации содер- жит указатель NEXT на первое неиспользуемое слово стека, начиная с которого будет образована следующая область ини- циализации. Третье слово содержит адрес возврата из вызова данного уровня; остальные слова содержат значения перемен- ных, используемые процедурами. На рис. 5.206 процедура MAIN вызвала процедуру SUB. В вершине стека образована новая область инициализации, на начало которой указывает регистр В. Указатели PREV и NEXT в обоих областях инициализации принимают указанные на ри- сунке значения. На рис. 5.20в процедура SUB вызывает рекур- сивно саму себя. В результате образовывается новая область инициализации для очередного уровня вызова процедуры SUB. .Обратите внимание, что адрес возврата и значения переменных Рис. 5.20. Рекурсивный вызов процедуры в случае использования автома- тического распределения памяти.
274 Гл. 5. Компиляторы для вызовов процедуры SUB различных уровней хранятся не- зависимо друг от друга. При возврате из процедуры в процедуру верхнего уровня текущая область инициализации, соответствующая данному уровню вызова, уничтожается. Указатель PREV в уничтожае- мой области используется для установки предыдущей области инициализации в качестве текущей, и работа программы про- должается. На рис. 5.20г изображено состояние стека после возврата из рекурсивного вызова процедуры SUB. Регистр В указывает на начало области инициализации предыдущего уровня вызова процедуры SUB. Адрес возврата и значения всех переменных в этой области инициализации в точности те же, что и были до рекурсивного вызова. Этот метод часто называют методом автоматического рас- пределения для того, чтобы отличить его от других методов динамического распределения памяти, которые осуществляются под управлением программиста. При использовании автомати- ческого распределения памяти компилятор должен генерировать код для ссылки на переменные с использованием каких-либо способов относительной адресации. В нашем примере компиля- тор связывает с каждой переменной адрес, являющийся относи- тельным адресом в области инициализации, вместо адреса ячейки внутри объектной программы. Поскольку в соответствии с принятым соглашением адрес начала текущей области иници- ализации находится на регистре В, то ссылка на переменную транслируется как инструкция с использованием относительной адресации по базовому адресу. Смещением в этой инструкции является относительный адрес переменной по области инициа- лизации. Компилятор должен также сгенерировать некоторый допол- нительный код для управления областями инициализации. В начале каждой процедуры должен содержаться код, образу- ющий новую область инициализации, связывающий ее с пре- дыдущей областью и устанавливающий необходимые указатели, как это изображено на рис. 5.20. Этот код часто называется прологом (prologue) процедуры. В конце процедуры должен содержаться код, уничтожающий текущую область инициали- зации и переставляющий соответствующим образом все указа- тели. Этот код часто называется эпилогом (epilogue). При использовании метода автоматического распределения памяти память для всех используемых в процедуре перемен- ных выделяется в момент вызова процедуры. Другие методы динамического распределения памяти дают программисту воз- можность самому определить тот момент времени, когда неко- торая память будет выделена. В ПЛ/1, например, ALLOCATE (А)
К д Машинно-независимые особенности компиляторов 275 в исходной программе транслируется в коды, которые выделяют намять для переменной А. Предложение FREE (А) освобождает память, занятую под А в предыдущем предложе- нии ALLOCATE. Это средство называется управляемым рас- пределением памяти в ПЛ/L Когда переменная образуется в результате выполнения оператора ALLOCATE, она автоматиче- ски становится доступной для использования в программе. Па- мять, выделенная под эту переменную в результате предыдущих операторов ALLOCATE, если они были, сохраняется в стеке и восстанавливается после освобождения текущей выделенной памяти. Другой тип динамического распределения памяти использу-ч ется в Паскале. Предложение NEW(P) выделяет память для переменной и устанавливает указатель Р на только что образованную переменную. Тип вновь образован- ной переменной определяется описанием указателя Р в про- грамме. Образованная переменная используется в программе также с помощью указателя Р. Предложение DISPOSE(P) освобождает память, отведенную под переменную, на которую ранее указывал Р. Подобные средства доступны также в ПЛ/1, где они называются базированным распределением памяти. Переменная, образованная динамически с использованием оператора NEW, не занимает фиксированной ячейки в области инициализации и, следовательно, не может быть адресована не- посредственно с использованием относительной адресации по базовому адресу. Доступ к таким переменным обычно осуще- ствляется с помощью косвенной адресации через указатель на переменную Р. Поскольку сам указатель занимает некоторую фиксированную ячейку в области инициализации, он может быть адресован обычным способом. Аналогичный метод приме- няется для управляемого распределения памяти в ПЛ/1. Об- ласть инициализации содержит указатель, который использует- ся для указания на память, выделенную в результате послед- него выполнения оператора ALLOCATE. Пока мы не обсуждали механизм выделения новой памяти для переменной. Один из подходов состоит в том, что на операцион- ную систему возлагается функция по распределению всей памя-. ти. Операторы NEW или ALLOCATE транслируются в запрос к операционной системе для получения области памяти требуе- мого размера. Другой метод состоит в выделении необходимой памяти с помощью библиотечных процедур компилятора^
276 Гл 5. Компиляторы В начале работы программы выделяется большой блок свобод- ной памяти, которая далее распределяется библиотечными про- цедурами, использующими некоторый стандартный метод рас- пределения памяти. Дальнейшее обсуждение и оценка методов распределения памяти содержится в Стендиш [1980]. Динамическое распределение памяти, рассмотренное в этом разделе, представляет собой другой пример отсроченного свя- зывания. Связь адреса с соответствующей переменной осуществ- ляется во время выполнения процедуры, а не во время компиля- ции или загрузки. Такое отсроченное связывание обеспечивает большую гибкость использования переменных и процедур. Этот метод, однако, требует больших накладных расходов, связанных с образованием областей инициализации, а также с использо- ванием косвенной адресации. (Аналогичные соображения были высказаны в конце разд. 3.4.2 в связи с динамическим связы- ванием.) 5.3.2. Структурированные переменные В этом разделе мы кратко обсудим компиляцию программ, использующих структурированные переменные, такие как мас- сивы, записи, строки и множества. Прежде всего мы сосредо- точимся на распределении памяти для подобных переменных и на генерации кода для доступа к ним. Эти вопросы достаточно подробно обсуждаются в применении к массивам. Те же прин- ципы, однако, могут быть использованы и для других типов структурированных переменных. Дальнейшие детали, связан- ные с этими проблемами, содержатся в ряде учебных пособий по компиляторам, таких как Ахо [1977] и Грис [1971]. Рассмотрим прежде всего описание массива в языке Пас- каль: А : ARRAY[1. .10] OF INTEGER Если каждая переменная типа INTEGER занимает одну ячейку памяти, то мы, естественно, должны выделить 10 слов для хра- нения этого массива. В более общем случае, если массив опи- сан как ARRAY[Z..u] OF INTEGER мы должны выделить под этот массив и — I + 1 слов памяти. Распределение памяти для многомерных массивов ненамного сложнее. Рассмотрим, например, двумерный массив В : ARRAY[0..3, 1..6] OF INTEGER Здесь первый индекс может принимать 4 различных значения (0—3)2 второй индекс может принимать 6 различных значений.
5.3. Машинно-независимые особенности компиляторов 277 Следовательно нам требуется 4 * 6 = 24 слова для хранения этого массива. В общем случае, если массив описан как ARRAYS. /2..и2] OF INTEGER требуемое количество слов памяти определяется выражением (щ — /14-1)* (и2 /2 4" 1) Для n-мерного массива требуемое количество слов определится в результате произведения и соответствующих множителей. Рассмотрим теперь вопрос генерации кода для доступа к массиву. Для этого существенно знать соответствие между эле- ментами массива и ячейками выделенной памяти. Для одномер- ных массивов это соответствие очевидно. Если для определен- ного ранее массива А первое слово будет содержать элемент А [1], то второе слово — А [2] и т. д. Для массивов большей размерности, однако, выбор необходимого соответствия не столь ясен. | 0.1 0,2 0,3 0,4 0,5 0,6 1,1 1.2 1,3 1,4 1,5 1.6 2,1 2.2 2,3 2,4 2,5 2,6 3,1 3.2 3,3 3,4 3,5 З.б| 1 Л J1 _ Л 1 Строка 0 Строка 1 Строка 2 Строка 3 а М 2,1 м м й 2.2 м 0.3 1.3 2.3 й °'4 м 2.4 3.4 0.5 1.5 2.5 3.5 0.6 1.6 2.6 |з.б| 1 ’1 11 „ f 1 И 11 г Столбец 1 Столбец 2 Столбец 3 Столбец 4 Столбец 5 Столбец 6 ...6 Рис. 5.21. Хранение массива В : ARRAY[0... 3, 1 .. 6]: а — по строкам и б — по столбцам. На рис. 5.21 изображены два способа хранения ранее опре- деленного массива В. На рис. 5.21а все элементы массива с одинаковым первым индексом располагаются в последователь- ных ячейках. Такой порядок расположения называется распо-* ложением по строкам. На рис. 5.216 все элементы с одинако- вым вторым индексом расположены вплотную друг к другу. Такое расположение называется расположением по столбцам. Другой способ описать эти соответствия состоит в том, чтобы последовательно перебирать все слова отведенной под массив памяти и рассматривать соответствующие изменения индексов. При расположении по строкам правый (второй) индекс изме- няется быстрее. При расположении по столбцам быстрее из- меняется левый индекс. Эти понятия могут быть обобщены на массивы, имеющие более двух индексов. Компиляторы для большинства языков высокого уровня располагают массивы по строкам; именно такого расположе- ния мы будем придерживаться в последующих рассуждениях,
278 Гл. 5. Компиляторы Однако исторически сложилось так, что большинство компилято- ров с Фортрана располагают свои массивы по столбцам. Для обращения к элементу массива мы должны вычислить адрес соответствующего элемента относительно базового адреса массива. Для обычного компьютера компилятор сгенерирует код, который разместит этот относительный адрес в индексном регистре. Далее для доступа к желаемому элементу массива будет использоваться индексная адресация. В дальнейшем мы будем предполагать, что базовым адресом является адрес пер- вого слова памяти, отведенной для хранения массива. Другие возможности рассмотрены в Ахо [1977]. Рассмотрим сначала одномерный массив А : ARRAYfl. .10] OF INTEGER и предположим, что в некотором предложении имеется ссылка на элемент массива А [6]. Элементу А [6] предшествуют пять элементов массива; на машине УУМ каждый элемент занимает три байта. Таким образом, адрес элемента А [6] относительно начального адреса массива будет равен 5*3=15. Если индексами элемента массива являются только констан- ты, вычисление относительного адреса можно осуществить в процессе компиляции. Если же индексы содержат переменные, компилятор обязан сгенерировать объектный код, осуществляю- щий эти операции во время выполнения программы. Предполо- жим, что массив описан следующим образом: А : ARRAY[Z..u] OF INTEGER и каждый элемент массива занимает w байтов памяти. Если значение индекса есть $, то относительный адрес элемента мас- сива А[$] определяется выражением w * (s — 1) Генерация кода, осуществляющего подобные вычисления, изо- бражена на рис. 5.22а. Запись А[/2] в четверке 3 означает, что сгенерированный машинный код должен обращаться к массиву А с помощью индексной адресации, поместив значение G в ин- дексный регистр. Для многомерных массивов генерация кода зависит от спо- соба хранения массива: по строкам или по столбцам. Мы бу- дем предполагать, что массивы хранятся по строкам. Рис. 5.21а иллюстрирует хранение массива В : ARRAY[0..3, 1..6] OF INTEGER по строкам. Рассмотрим сначала элемент массива В [2, 5]. Если мы начнем с начала массива, то мы должны пропустить две полные строки (строки 0 и 1), прежде чем окажемся в начале строки 2 (которая начинается с элемента В [2, 1]) ¥ Каждая та-
5.3, Машинно-независимые особенности компиляторов 279 кая строка содержит 6 элементов, две строки содержат 2*6 = — 12 элементов массива. Мы должны также пропустить первые 4 элемента в строке 2, чтобы добраться до элемента В [2, 5]. Вместе это составляет 16 элементов массива между его началом и элементом В [2, 5]. Если каждый элемент зани- мает три байта, то относительный адрес внутри массива элемента В А '• Ii*»io] от integer [2, 5] будет равен 48. • В более общем случае пред- А[1] 5 положим, что массив описан как В : ARRAY[lt..uo l2..u2] OF INTEGER * <D - i i, и мы хотим обратиться к элемен- ту массива с индексами, имеющи- (2) * *1 *2 ми значения Si и s2. Относитель- <э) is a[i21 ный адрес элемента В [sb s2] а дается выражением w * {(Sj lj) * (u2 12 “Ь 04“ + (s2-l2)} Рис. 5.226 иллюстрирует процесс генерации кода для обращения к такому массиву. Вы должны вни- мательно изучить приведенную последовательность четверок, что- бы убедиться в том, что вы по- нимаете изложенные вычисле- ния. Приведенные выше методы и формулы легко могут быть обоб- щены на массивы большей раз- мерности. Детали можно найти в Ахо [1977]. В таблице символов описание В : ARRAY [0..3.1. ,6] Of IMTEGEE ВЦ,Л s- 5 (1) * I #6 ij (2) - J #1 i2 <3) + Jj i2 i3 (4) * «j ii (5) -« is A[i4! 6 Рис. 5.22. Генерация объектного кода для доступа к элементам массива. массива содержит тип элементов массива, размерность массива, верхние и нижние границы мас- сива для каждого индекса. Этой информации достаточно для того, чтобы компилятор мог сгенерировать код, необходимый для обращения к массиву. В некоторых языках, однако, необ- ходимая информация во время компиляции бывает еще не из- вестна. В ПЛ/1, например, двумерный массив может быть опре- делен как Т (I: J— 1,1 + 1: J)
280 Гл. 5. Компиляторы Эта запись означает, что первый индекс изменяется от I до J—1, а второй индекс находится в пределах от 1 + 1 до J< Здесь I и J — имена переменных, определенных в программе. Очевидно, что для такого массива память можно отвести, лишь используя какой-либо из методов динамического распределения памяти, В ПЛ/1 это можно осуществить с помощью опера- тора ALLOCATE Т Предполагается, что переменным I и J уже присвоены некото- рые значения. Поскольку значения переменных I и J во время компиляции неизвестны, компилятор не может непосредственно сгенериро- вать код, подобный изображенному на рис. 5.22. Вместо этого компилятор образовывает описатель массива, содержащий ячейки для запоминания верхней и нижней границ всех индек- сов массива. К моменту выделения памяти под такой массив все границы индексов должны быть вычислены и запомнены в описателе массива. Для обращения к массиву генерируется код, использующий необходимые значения из описателя массива для вычисления требуемого относительного адреса. Описатель мас- сива может также включать в себя размерность массива, тип элементов массива и указатель на его начало. Подобная ин- формация может быть полезна в том случае, когда размещен- ный в памяти массив требуется передать в качестве параметра другой процедуре. Рассмотренные для массивов проблемы также возникают при компиляции других структурированных переменных (запи- сей, строк и множеств). Компилятор должен: выделить необхо- димую для размещения переменной память; запомнить инфор- мацию, характеризующую структуру переменной; сгенерировать, используя эту информацию, код для обращения к компонентам структурированной переменной; породить описатель структури- рованной переменной для тех случаев, когда необходимая ин- формация отсутствует во время компиляции. Дальнейшее об- суждение этих проблем для различных специальных типов структурированных переменных можно найти в Ахо [1977] и Грис [1971]- 5.3.3. Машинно-независимая оптимизация кода В этом разделе мы обсудим некоторые важные методы ма- шинно-независимой оптимизации кода. Так же как и в предыду- щих разделах, мы не будем стремиться к детальному описанию какого-либо из этих методов. Вместо этого мы дадим словесное описание и проиллюстрируем основные понятия примерами. Ал- горитмы и детали, касающиеся этих методов, можно найти в
5.3. Машинно-независимые особенности компиляторов 281 работах Ахо [1977] и Грис [1971]. Мы будем предполагать, что исходная программа уже оттранслирована в последователь- ность четверок так же, как в разд. 5.2.1. Одним из важных источников оптимизации кода является удаление общих подвыражений, т. е. подвыражений, которые встречаются в нескольких местах программы и вычисляют одно и то же выражение. Рассмотрим, например, предложение на рис. 5.23а. Терм 2*J является общим подвыражением. Опти- мизирующий компилятор должен только один раз сгенерировать код, вычисляющий это умножение, и использовать его резуль- тат в обоих местах. Общие подвыражения обычно обнаруживаются при анализе промежуточной формы представления программы. Подобная промежуточная форма изображена на рис. 5.236. Если мы ис- следуем эту последовательность четверок, то обнаружим, что четверки 5 и 12 совпадают, за исключением имени получаемого промежуточного результата. Обратите внимание также, что операнд J не меняет своего значения между четверками 5 и 12. Невозможно достичь четверки 12, не проходя предварительно четверку 5, поскольку они расположены на одном линейном участке. Таким образом, четверки 5 и 12 вычисляют одно и то же значение. Это означает, что мы можем удалить четверку 12 и заменить любые обращения к ее результату (ij0) на об- ращение к переменной i3, которое является результатом четвер- ки 5. Эта модификация позволяет избежать дублирования вы- числений подвыражения 2*J, которое мы выделили как общее подвыражение при анализе исходной программы. После замены i3 на /ю мы обнаружим, что четверки 6 и 12 также совпадают, за исключением имени результата. Следо- вательно, мы можем удалить четверку 13 и заменить перемен- ную ц всюду, где она используется, на переменную /ц. Анало- гично четверки 10 и 11 также могут быть удалены, поскольку они эквивалентны четверкам 3 и 4. Результат применения этого метода изображен на рис. 5.23в. Четверки на этом рисунке перенумерованы, но, чтобы легче было сравнивать с рис. 5.236, имена промежуточных результа- тов ij оставлены неизменными, за исключением вышеназван- ных замен в скобках. Обратите внимание, что общее количество четверок сокращено с 19 до 15. Поскольку операции во всех используемых здесь четверках займут, вероятно, примерно оди- наковое время на обычном компьютере, то мы также сократим общее время выполнения программы. Другим источником оптимизации кода является удаление инвариантов цикла. Так называются подвыражения внутри цик- ла, результирующие значения которых не изменяются внутри никла при переходе от одной итерации к другой. Таким об* разом, эти значения могут быть вычислены только один раз
282 Гл. 5. Компиляторы Х,Х S ДШ[1<. 10,1.. 10] OF INTEGER FOR I i- 1 ТО 10 DO XU,2*J-1] := YII,2*J] a (1) :=» #1 I ( инициализация цикла} (2) JGT I #10 (20) (3) - I #1 *1 4вычисление индексов длях} (4) * *1 #10 *2 (5) * #2 J *3 (6) - *3 #1 *4 (7) - Ч #1 *5 (8) + iz *5 *6 (9) * Ч *7 (10) - I #1 *8 (вычисление индексов для (И) * *8 #10 *9 (12) * iz *10 (13) - *10 #1 *11 (14) *11 *12 (15) * *12 #3 *13 (16) ’^З1 Х1171 {операция присваивания) (17) + #1 I *14 (конец цикла) (18) :=* *14 Т (19) J (2) (20) 6 (следующее предложение} Рис. 5.23. Оптимизация объектного кода, связанная с общими подвыраже- ниями и инвариантами цикла. перед входом в тело цикла вместо того, чтобы вычислять их заново перед каждой итерацией. Поскольку для большинства программ основное время работы приходится на выполнение циклов, экономия времени от подобной оптимизации может быть весьма существенной. Мы предполагаем существование алгоритмов поиска циклов на основе анализа блок-схемы
(1) • *• #1 1 ” {ййицимйяцяя цикла} (2) JGT I #10 (16) <3) - I ii 4 {вычисление индексов для X} (4) * ч #10 i2 (5) * #2 л 4 (6) - ч и -4 (7) - ч #i 4 (8) + ч i5 4 (9) * ч #3 4 (Ю) + Ч *4 Ч2 (вычисление яйдекеъв для Y } (П) * Чг #3 Чз (12) : = ’•Чз1 X[i7l- (операция присваивания} (13) + #1 I Ч4 {конец цикла} (14) ; =а '44 I (15) J (2) (1J) (следующее предложение} В (1) * #2 J 4 (вычисление инвариантов } (2) - ч #1 4 (3) ч #1 4 (4) • а fl I {инициализация цикла } (5) JGT I #10 (16) (6) - I n 4 (вычисление индексов для X} (7) * ч #10 4 (8) + i2 *5 4 (9) * ч #3 4 ,(Ю) + i2 4 Ч2 .{вычисление индексов для Y} (И> * Ч2 #3 Чз (12) • л ’U13 I x [1 7) (операция присваивания } (13) + #1 I' ’44 {конец цикла} (14) • ав Ч4 I \(15) J (5) (следующее предложение} (16) г Рис, 5,23, Продолжение.
?84 - Гл. 5. Компиляторы Программы. Одним из примеров подобного алгоритма является метод построения блок-схемы программы, описанный в разд. 5.2.2. Примером инварианта цикла является вычисление выражения 2*J на рис. 5.23а (см. четверку 5 на рис. 5.23в). Результат вы- числения этого выражения зависит только от операнда J, зна- чение которого не изменяется во время выполнения цикла. Таким образом, мы можем поместить четверку 5 на рис. 5.23в непосредственно перед началом выполнения цикла. Аналогичные соображения справедливы относительно четверок 6 и 7. На рис. 5.23 изображена последовательность четверок, яв- ляющихся результатом описанных модификаций. Общее коли- чество четверок остается тем же, что и на рис. 5.23, но коли- чество четверок внутри тела цикла уменьшилось с 14 до 11. (Каждое выполнение предложения FOR на рис. 5.23а вызывает 10-кратное выполнение тела цикла. Это означает, что общее количество операций, необходимых для выполнения FOR, со- кратилось со 141 до 114. Наши модификации сократили общее количество операций, приходящееся на одно выполнение предложения FOR, со_ 181 (рис. 5.236) до 114 (рис. 5.23г), что существенно уменьшило время выполнения программы. Существуют также и более тон- кие методы обработки общих подвыражений и инвариантов цикла, чем описанные выше. Можно ожидать, что благодаря этим методам может быть получен еще более оптимизирован- ный код. Примеры и обсуждение этих методов содержатся в работе Ахо [1977]. Некоторую оптимизацию можно, конечно, осуществить путем переписывания исходной программы. Например, предложения на рис. 5.23а можно переписать следующим образом: TI := 2*J Т2 :== TI —1; FOR I := 1 ТО 10 DO Х[1, 12] := V [I, Т1]; В результате будет достигнута лишь частичная по сравнению с только что описанной оптимизация кода. Дальнейшая оптими- зация связана с процессом вычисления относительного адреса индексированных переменных, на которые запись исходной про- граммы не может повлиять. Например, оптимизацию, связан- ную с четверками 3, 4, 10 и 11 на рис. 5.236, нельзя осущест- вить путем каких-либо преобразований исходной программы. Следует заметить также, что исходные предложения на рис. 5.23а предпочтительнее, поскольку они нагляднее, чем мо- дифицированная версия программы, использующая переменные Т1 и Т2. Оптимизирующий компилятор должен предоставить программисту возможность писать исходную программу такв
5.3. Машинно-независимые особенности компиляторов 285 3)0 10 I » 1,20 10 TABLE(l) « 2**1 а <1) : = и I {иямциализзцмя цикла} (2) ЕХР а I ч {вычисление 2**1} (3) «* I п i2 (вычисление индексов)» <*) * i2 #3 ч <5) ; = Ч IABUH Л {операция присваивания} (6) + I п ч {конец Цикла} (7) *ж Ч I (8) JLE I по (2) 6 (*) J ж tl ч {начальные присваиваний } (2) К-З) ч (3) : = #1 I {инициализация цикла} (*Г Ч #2 ч {вычисление 2**1} (5) + Ч п ч {вычисление индексов} <6) ; =? ч IABLE[i3] {операция присваивания} (7) 4* I п ч {конец цикла} (8) $ =* ч I (9) I в но Рис. 5.24. Оптимизация объектного кода за счет замены одних операций на другие, более эффективные. чтобы добиваться простоты и прозрачности, должен компили- ровать программу в машинные коды таким образом, чтобы до- биться эффективности выполнения. Еще один источник оптимизации кода состоит в замене ме- нее эффективных операций на более эффективные. Рассмотрим в качестве примера фрагмент программы на Фортране, изо- браженной на рис. 5.24а. Изображенный цикл DO порождает таблицу, содержащую первые 20 степеней двойки. При каждом проходе по телу цикла константа 2 возводится в степень I. На рис. 5.246 изображено представление этих предложений в виде последовательности четверок. Возведение в степень представле- но операцией ЕХР. На уровне машинного кода операция ЕХР
286 Гл. 5 Компиляторы представляет собой либо цикл, осуществляющий последователь- ность умножений, либо вызов подпрограммы, использующий ло- гарифмы для получения результата. Анализируя эту программу, мы можем обнаружить, что су- ществует более эффективный путь выполнения этих вычисле- ний. При каждом проходе по циклу величина I увеличивается на 1. Таким образом, величина 2**1 для текущего прохода по циклу может быть получена путем умножения на 2 зна- чения, полученного на предыдущем проходе. Очевидно, что этот метод вычисления выражения 2**1 является существенно бо- лее эффективным, чем выполнение серии умножений или ис- пользование логарифмирования. Подобные модификации могут быть осуществлены при вы- числении относительного адреса элемента массива TABLE(I). Предположим, что каждый элемент массива занимает одно сло- во, тогда объектная программа для УУМ будет вычислять сме- щение по формуле 3*(1 — 1). Это вычисление осуществляется в четверках 3 и 4 на рис. 5.246. Таким образом, объектная программа будет выполнять одно умножение при каждом об- ращении к массиву. Описанный метод повышения эффективности применим и в Этом случае. Поскольку на каждом проходе по циклу после- довательно осуществляется обращение к соседним элементам массива, то вычисление требуемого смещения может быть по- лучено путем сложения предыдущего смещения с 3. Поскольку сложение на УУМ происходит быстрее умножения, подобное преобразование приведет к более эффективному объектному коду. На рис. 5.24в приведена модифицированная по сравнению с рис. 5.246 последовательность четверок, в которой осуществлены обе указанные выше возможности повышения эффективности. Алгоритм, осуществляющий подобные модификации, содержится в работе Грис [1971]. Как и в нашем предыдущем примере, подобную оптимизацию можно частично осуществить на уровне исходной программы. Однако оптимизировать вычисления ин- дексов массива программист не может, поскольку он не может влиять на детали кода, генерируемого для обращения к мас- сиву. Существует также ряд других возможностей машинно-неза- висимой оптимизации кода. Например, вычисления значения операндов, известных в момент компиляции, могут быть осуще- ствлены непосредственно компилятором. Другие методы опти- мизации включают в себя преобразование цикла в линейный участок (развертывание цикла) и слияния тел различных цик- лов. Детали этих и других методов оптимизации см. в Грис }1971] и Ахо [1977],
5.3. Машинно-независимые особенности компиляторов 287 5.3.4. Блочно-структурированные языки В некоторых языках, подобных Алголу, программа разбива- ется на части, называемые блоками. Блок — это часть програм- мы, в которой можно описать свои собственные идентификато- ры. Определению блока также удовлетворяют такие программ- ные единицы, как процедуры и функции в Паскале. В этом разделе мы обсудим некоторые вопросы, связанные с компиля- цией и выполнением программ, написанных на таких блочно- структурированных языках. На рис. 5.25а изображена схема блочно-структурированной программы на Паскале. Каждой процедуре соответствует блок. В последующем обсуждении мы будем использовать термины процедура и блок как синонимы. Обратите внимание, что бло- ки могут вкладываться друг в друга. Например, процедуры В и D вложены в процедуру А, а процедура С вложена в проце- дуру В. Каждый блок может содержать, как это изображено на рисунке, описания переменных. В блоке допустимы также обращения к любым переменным, которые описаны в любом блоке, содержащем рассматриваемый блок, при условии что эти имена не переопределены во внутреннем блоке. Рассмотрим, например, переменные X, Y, Z типа INTEGER, определенные в строке 2 процедуры А. Процедура В содержит описание переменных X и Y, имеющих тип REAL в строке 4. Внутри процедуры В использование имени X означает ссылку на переменную типа REAL, определенную внутри В. Однако использование имени Z означает обращение к переменной типа INTEGER, определенной в блоке А, поскольку имя Z не пере- определено внутри В. Аналогично внутри процедуры S имя W связано с переменной, определенной внутри С; имена X и Y будут связаны с переменными, определенными в В, и имя Z связано с переменной, определенной в А. Переменные не могут использоваться вне того блока, внутри которого они определе- ны. Например, именем W нельзя пользоваться вне процедуры В, а именем V нельзя пользоваться вне процедуры С. При компиляции программ, написанных на блочно-структу- рированных языках, удобно нумеровать блоки, как это изобра- жено на рис. 5.25а. Как только распознано начало нового бло- ка, этому блоку присваивается очередной номер. В результате компилятор может построить таблицу, описывающую блочную структуру программы так, как это изображено на рис. 5.256. В столбце «Уровень вложенности» содержится глубина вложен- ности блоков. Самый внешний блок имеет уровень вложенности 1, а все остальные блоки имеют уровень вложенности, на 1 больший, чем уровень вложенности охватывающего блока. По- скольку имя может быть определено более одного раза (в раз- ных блоках), каждый элемент таблицы символов, содержащий
288 Гл. 5. Компиляторы 1 2 3 4 PROCEDURE А, VAR X.Y.Z : INTEGER; PROCEDURE B;_________ VAR. W,X,Y : REAL; 5 е PROCEDURE С;___________ VAR V,W : INTEGER; 3 7 8 9 10 11 12 END {С}; END {B}; PROCEDURE D;________ VAR X,Z : CHAR; END {D}; END. {A} 4 a Уровень Имя Номер вложен-* Объемлющий блока блока ности блок п .. — ——* * ' 1 А 1 1 — В 2 2 1 С 3 3 2 D 4 2 1 6 Рис. 5.25. Программа с вложенными блоками. идентификатор, должен содержать также номер того блока, в котором этот идентификатор описан. Описание идентификатора допустимо, если этот идентификатор не был ранее описан в том же блоке. В противном случае таблица символов содержа- ла бы несколько элементов, соответствующих одному имени* Элементы таблицы символов, соответствующие описанию одного и того же имени в разных блоках, могут быть связаны между собой в таблице символов цепочками указателей,
5.3. Машинно-независимые особенности компиляторов 289 Когда компилятор встречает ссылку на идентификатор в исходной программе, то прежде всего он должен проверить таб- лицу символов на наличие описания этого идентификатора в текущем блоке. Если подобное описание отсутствует, компиля- тор должен проверить наличие такого описания в блоке, объ- емлющем текущий, затем в объемлющем блоке следующего уровня и т. д. Если описание этого идентификатора не найдено даже в блоке самого верхнего уровня, то использование такого идентификатора является ошибкой. Только что описанный процесс поиска может быть легко реализован, если таблица символов построена с использованием хеширования. Хеш-функция используется для поиска какого- либо одного определения нужного идентификатора. Далее ком- пилятор осуществляет поиск необходимого элемента в таблице символов, идя по цепочке описаний данного идентифика- тора. Существуют также другие способы организации таблицы символов, при которых описания соответствующих идентифи- каторов хранятся с учетом структуры вложенности блоков, в которых они находятся. Такого рода структуры позволяют оптимизировать поиск нужного описания. См., например, Ахо 11977]. Большинство блочно-структурированных языков использует автоматическое распределение памяти, описанное в разд. 5.3.1. Переменные, описанные в данном блоке, располагаются в об- . ласти инициализации, которая порождается заново каждый раз при входе в блок. Если в некотором предложении используется переменная, описанная внутри текущего блока, то эта перемен- ная находится в текущей области инициализации и может ис- пользоваться обычным способом. Однако можно также исполь- зовать переменные, описанные в некотором объемлющем блоке. В этом случае для доступа к переменной должна быть найдена самая поздняя область инициализации того блока, в которой эта переменная описана. Один из широко распространенных методов осуществления доступа к переменным, описанных в объемлющем блоке, ис- пользует структуру данных, которую мы назовем дисплеем (display). Дисплей содержит указатель на самую последнюю область инициализации текущего блока и на все блоки, объ- емлющие данный в исходной программе. Если в некотором блоке осуществляется обращение к переменной, описанной в некотором объемлющем блоке, то генерируется объектный код, который, используя дисплей, ищет область инициализации, со- держащую нужную переменную. Использование дисплея иллюстрируется на рис. 5.26. Пред- полагается, что процедура А вызвана системой. Далее про- цедура А вызвала процедуру В, а процедура В вызвала про- цедуру С. Сложившаяся ситуация изображена на рис, 5.26а< 10 Зак. 792
290 Гл. 5. Компиляторы Дисплей Дисплей Стек б Стек а Стек Стек Дисплей Рис. 5.26. Использование дисплея для процедур, изображенных на рис. 5.25< Дисплей 6 Стек содержит области инициализации, соответствующие вызо- вам процедур А, В и С. Дисплей содержит указатели на об- ласть инициализации процедуры С и на области инициализации объемлющих блоков (А и В). Предположим теперь, что процедура С вызывает рекурсивно саму себя, В результате такого вызова в стеке образовываются
5.3. Машинно-независимые особенности компиляторов 291 новые области инициализации для процедуры С. Все описанные в С переменные должны теперь быть расположены в этой по- следней области инициализации; указатель на С, содержащийся в дисплее, должен быть также модифицирован. Поскольку пе- ременные, соответствующие предыдущему вызову процедуры С, не доступны в данный момент, то дисплей не содержит указа- теля на эту область инициализации. Эта ситуация изображена на рис. 5.266. Предположим теперь, что процедура С вызывает процедуру D. (Это допустимо, поскольку процедура D определена в про- цедуре А, содержащей С.) Получившееся в результате такого вызова содержимое стека в дисплее изображено на рис. 5.26в. Область инициализации для процедуры В образована обычным образом и записана в стек. Обратите внимание, что дисплей содержит в этот момент только два указателя: на области ини- циализации процедур D и А. Это объясняется тем, что про- цедура D не может использовать переменных, описанных в В или С (за исключением случая, когда они переданы ей как параметры), даже несмотря на то, что она вызвана из С. В соответствии с правилами видимости для блочно-структури- рованных языков процедура D может использовать только пе- ременные, описанные в D или в объемлющем D блоке исход- ной программы (которым в нашем случае является процеду- ра А). Сходная ситуация, изображенная на рис. 5.26г, образуется, если теперь процедура D вызовет В. Процедура В может ис- пользовать только те переменные, которые описаны в В или в А, что и отражено в списке указателей, находящемся в дис- плее. После того как процедура В завершится и передаст уп- равление процедуре D, содержимое стека и дисплея опять ока- жется таким, как на рис. 5.26в. Важно все время помнить о разнице между динамическим выделением памяти для переменных, которое соответствует об- ластям инициализации в стеке, и правилами доступа к пере- менным в блочно-структурированной программе, которые отра- жает содержимое дисплея. Вы должны внимательно изучить рис. 5.25 и 5.26 для того, чтобы убедиться, что вы действитель- но понимаете, почему стек и дисплей будут иметь именно то содержимое, какое изображено на этих рисунках. Компилятор для блочно-структурированного языка должен содержать в себе код, используемый в начале каждого блока для инициализации дисплея, соответствующего данному блоку, В конце блока он должен обращаться к коду, восстанавливаю- щему предыдущее содержимое дисплея. Детали реализации со- держатся в Ахо [1977]. 10*
292 Гл. 5. Компиляторы 5.4. Варианты построения компиляторов В этом разделе мы рассмотрим некоторые возможные аль- тернативы построения компиляторов. Изложение будет по не- обходимости довольно кратким. Наша цель — ввести основные понятия и концепции, а не подробно обсуждать какие-либо во- просы. В разд. 5.1 была описана простая однопросмотровая схема компиляции. В разд. 5.2 и 5.3 представлены особенности ком- пиляторов, требующие для своей реализации более одного про- смотра. В разд. 5.4.1 мы кратко обсудим общую проблему раз- биения процесса компиляции на отдельные просмотры и рас- смотрим преимущества однопросмотровых и многопросмотровых вариантов построения компиляторов. Разд. 5.4.2 посвящен интерпретаторам, которые преобразуют исходную программу в некоторую промежуточную форму и вы- полняют ее вместо трансляции в машинные коды. В следую- щем разд. 5.4.3 мы вводим связанное с интерпретаторами по- нятие компилятора на псевдокод, который преобразует програм- мы на языках высокого уровня в объектные коды некоторой гипотетической машины. В заключительном разд. 5.4.4 описываются системы построе- ния компиляторов, использующие программные средства для автоматизации многих этапов процесса создания компилятора. 5.4.1. Разбиение на отдельные просмотры В разд. 5.1 была описана простая однопросмотровая схема компиляции с подмножества языка Паскаль. Ее основу состав- ляет процесс грамматического разбора. Лексический сканер вы- зывается, когда для грамматического разбора требуется очеред- ная входная лексема; обращение к программам генерации объ- ектного кода происходит как только распознана очередная кон- струкция языка. Получаемый таким образом объектный код не- достаточно эффективен, поскольку большинство рассмотренных в разд. 5.2 и 5.3 приемов оптимизации не применимо в одно- просмотровых компиляторах. С другой стороны, процесс ком- пиляции, требующий только одного просмотра по программе и не осуществляющий преобразований программы в промежуточ- ные представления, является весьма быстрым. Не все языки допускают однопросмотровую компиляцию. Описания переменных в Паскале должны предшествовать опе- раторам, использующим эти переменные. В Фортране перемен- ные могут быть описаны только в начале программы; тип не- описанные переменных определяется по умолчанию. Некоторые языки, в частности ПЛ/1, допускают появление описаний иден- тификаторов после их использования. Однопросмотровые ком-
5.4. Варианты построения кбмйиляторов 293 лиляторы могут обрабатывать ссылки вперед в операторах пе- рехода, используя приемы, сходные с описанными для одно- просмотровых ассемблеров. Гораздо более серьезную проблему представляет собой использование элементов данных, описания которых еще не появились. Рассмотрим, например, следующий оператор присваивания: X := Y*Z Если все переменные X, Y и Z целого типа, то объектным ко- дом этого оператора могут быть команда целочисленного умно- жения и команда запоминания результата. Если же одни пере- менные целого типа, а другие вещественного, то в объектном коде должны присутствовать как минимум одна команда пре- образования типа и команда умножения с плавающей точкой.» Очевидно, что компилятор не сможет скомпилировать правиль- ный объектный код до тех пор, пока ему не станут известны типы всех переменных. Для некоторых сочетаний типов пере- менных оператор может оказаться вообще недопустимым. Та- ким образом, язык, разрешающий использование элементов дан- ных до их описания, не допускает однопросмотровой компи- ляции. Некоторые языки программирования по другим причинам требуют при компиляции более двух просмотров. Например, Хантер [1981] показал, что Алгол-68 требует как минимум трех просмотров. Существует несколько факторов, которые должны учиты- ваться при выборе однопросмотровой или многопросмотровой схемы компиляции (в предположении, что рассматриваемый язык в принципе допускает однопросмотровую компиляцию). Если важна скорость компиляции, то однопросмотровая схема может оказаться предпочтительней. Например, при счете сту- денческих задач большая часть времени, как правило, уходит на компиляцию. Полученные во время компиляции объектные модули используются лишь один-два раза; время счета студен- ческих задач обычно мало. Ускорение процесса компиляции в этих условиях может привести к существенному повышению эффективности использования ЭВМ и к сокращению времени между повторными выходами на машину. Если же скомпилированные объектные модули используются многократно или эти модули обрабатывает болыйие массивы данных, то скорость выполнения программы становится более важным фактором, чем скорость ее компиляции. В этом случае предпочтительнее может оказаться много!фосмотровая схема компиляции, позволяющая использовать более сложную технику Оптимизации объектного кода. Многопросмотровые компилято- ры используются также тогда, когда доступная оперативная йамять и другие ресурсы системы существенно ограничены.)
294 Гл. 5. Компиляторы Ресурсы, требуемые для каждого просмотра, могут быть умень- шены с увеличением количества просмотров. На выбор схемы компилятора влияют также и другие фак- торы. Если компиляция разбита на несколько просмотров, то каждый просмотр упрощается и, следовательно, становится лег- че для понимания, программирования и отладки. Разработка отдельных просмотров может быть поручена разным програм- мистам, которые могут работать параллельно. В результате уменьшается общее время, требуемое на разработку компиля- тора. Дальнейшее обсуждение вопроса о разбиении компиляции на отдельные фазы можно найти в работах Хантер [1981]1, Ахо [1977]. Примеры реальных компиляторов и детальное опи- сание разбиения всей работы на отдельные этапы содержатся в книгах Грис [1977] и Ахо [1977] < 5.4.2. Интерпретаторы Так же как и компилятор, интерпретатор обрабатывает ис- ходную программу, написанную на языке высокого уровня. Ос- новное различие состоит в том, что интерпретатор непосредст- венно исполняет некоторое представление исходной программы, а не транслирует его в машинные коды. Интерпретаторы обычно выполняют, подобно тому как это было описано для компиляторов, лексический и синтаксический анализ и транслируют исходную программу в свое внутреннее представление. При этом возможно использование самых раз- личных внутренних представлений. Одно из них — это последо- вательности четверок, подобные описанным в разд. 5.2. Чаще используется расширенная польская постфиксная запись (см. Грис [1971]). В качестве внутреннего представления можно ис- пользовать даже исходную форму записи программы, хотя обычно гораздо более эффективной является предварительная обработка исходной программы, предшествующая ее выпол- нению. После трансляции исходной программы во внутреннее пред- ставление интерпретатор выполняет заданные ею операции. На фазе выполнения интерпретатор можно рассматривать как на- бор подпрограмм. Вызов этих подпрограмм осуществляется под управлением внутреннего представления программы. Трансляция исходной программы в некоторое внутреннее представление проще и быстрее, чем компиляция в машинные коды. Однако выполнение интерпретатором внутреннего пред- ставления программ осуществляется гораздо медленнее, чем вы- полнение машинных кодов, сгенерированных компилятором. Таким образом, интерпретатор обычно не следует использовать,
5.4. Варианты построения компиляторов 295 когда важна скорость выполнения программы. Если же важнее оказывается скорость трансляции и время выполнения програм- мы невелико, то предпочтение может быть отдано интерпрета- тору. Существенное преимущество интерпретатора по сравнению с компилятором состоит в тех средствах отладки, которые легко могут быть в нем реализованы. Интерпретатор обычно имеет таблицу идентификаторов, номера строк и другую информацию об исходной программе. Она может быть использована во вре- мя выполнения программы для автоматической распечатки по- именованных данных, трассировки операторов исходной про- граммы с указанием номеров строк, в которых они содержатся, и т. д. Следовательно, особенно привлекательным является ис- пользование интерпретатора в учебном процессе, когда акцент делается на понимание и отладку программ. Детали, относя- щиеся к реализации отладочных средств в интерпретаторах, можно найти в работе Грис [1971]. Большинство языков программирования может достаточно успешно как интерпретироваться, так и компилироваться. Не- которые языки весьма хорошо приспособлены для интерпрета- ции. Как мы уже видели, компиляторы обычно генерируют об- ращения к библиотечным программам для выполнения таких функций, как ввод-вывод и сложные операции преобразования данных. Для таких языков, как Снобол и АПЛ, большая часть скомпилированной программы состоит из обращений к „таким библиотечным программам. В подобных случаях интерпретатор может оказаться предпочтительней из-за большей скорости трансляции. Основное время выполнения программы будет занимать работа стандартных библиотечных программ неза- висимо от того, используется ли компилятор или интерпре- татор. Особенности некоторых языков могут быть естественно ре- ализованы только в режиме интерпретации. В языках АПЛ и Снобол тип переменной может меняться во время выполнения программы. В АПЛ переменные, доступные внутри функции или подпрограммы, определяются динамически последовательностью вызовов во время выполнения программы, а не статической вложенностью блоков исходной программы (см. рис. 5.25 и 5,26, иллюстрирующие это различие). Языки, допускающие динами- ческое переопределение типов и областей действия имен, очень трудно эффективно компилировать. Их проще реализовать в режиме интерпретации, который позволяет легко связывать символические имена переменных с типами данных и ячейками памяти в ходе выполнения программы. Дальнейшее обсуждение разработки и использования интер- претаторов имеется в работе Грис [1971].
296 - Гл. 5. Компиляторы 5.4.3. Компиляторы на Р-код Компилятор на Р-код Интерпрета- тор Р-кода Рис. 5.27. Компиляция и выпол- нение программы нием компилятора с использова- на Р-код. Основные идеи, положенные в основу компиляторов на псев- докод (Р-код) и интерпретаторов, весьма схожи. В обоих слу- чаях исходная программа анализируется и преобразуется во внутреннее представление, которое затем выполняется в режи- ме интерпретации. В случае компиляторов на Р-код форма внутреннего представления является машинным языком неко- торой гипотетической ЭВМ, часто называемой псевдомашиной. Процесс использования компиля- тора на Р-код изображен на рис. 5.27. В результате компиля- ции получается объектная иро- грамма в P-кодах. Далее эта про- грамма читается и выполняется интерпретатором Р-кода. Основным достоинством тако- го подхода является легкая пе- реносимость программного обес- печения на другие типы ЭВМ. Компилятору не нужно генериро- вать разные машинные коды для разных ЭВМ, поскольку . объект- ные программы на псевдокоде могут выполняться на любой ЭВМ, имеющей интерпретатор P-кода. Сам компилятор тоже мо- жет быть перенесен на другие ЭВМ, если он написан на своем собственном входном языке. Для этого исходная программа компилятора должна быть ском- пилирована на Р-код, после чего она может интерпретироваться на других ЭВМ. Таким образом, компилятор на Р-код может использоваться без каких-либо изменений для широкого класса ЭВМ, если для каждой из них написан свой интерпретатор P-кода. Написание этого интерпретатора хотя и достаточно сложно, но, конечно же, проще написания нового транслятора. *Гакой подход может быть использован для переноса и других типов системного программного обеспечения без его переписы- вания. В качестве примера можно привести описанную в разд. 6.5 систему UCSD Pascal. Устройство псевдомашины и соответствующего ей Р-кода часто вытекает из особенностей компилируемого языка. Напри- мер, псевдомашина для компилятора с Паскаля может иметь такие команды, как вычисление индексов массива, вход и вы- ход из подпрограммы, элементарные операции над множества- ми. Наличие таких емких команд упрощает процесс генерации объектного кода, делает компилятор меньше и эффективнее.
5.4. Варианты построения компиляторов 297 Кроме того, объектная программа на P-коде часто оказывается намного короче, чем соответствующая ей программа в машин- ных кодах обычной ЭВМ. Это особенно существенно для машин с жестко ограниченным объемом оперативной памяти. Очевидно, что выполнение программ на P-коде в режиме интерпретации может оказаться намного медленнее, чем вы- полнение эквивалентных машинных команд. В некоторых си- стемах это оказывается, однако, несущественным. Многие ком- пиляторы на P-код были разработаны для использований в однопользовательском режиме на персональных микроЭВМ. В этом случае время выполнения программы может оказаться не очень важным, если общая производительность системы ограничена временем, необходимым пользователю для обдумы- вания своих действий: В тех случаях, когда время выполнения программы все же хотелось бы уменьшить, некоторые компиляторы на Р-кОд по- зволяют использовать подпрограммы в машинных кодах. Пере- писав с P-кода в машинные коды небольшое число наиболее часто используемых программ, как правило, можно достичь су- щественного увеличения общей производительности. При этом, конечно, приходится частично жертвовать легкостью переноса программ на Р-коде. Наиболее широко известный компилятор на Р-код — это UCSD Pascal компилятор, описанный в разд. 5.5.2. Часто сами понятия псевдомашины и P-кода связывают только с этим ком- пилятором. На самом деле эти понятия являются более общи- ми и используются в ряде других программных систем, напри- мер в компиляторе на P-код, описанном в работе Нори [1981]. 5.4.4. Компиляторы компиляторов Написание компиляторов обычно связано с большими уси- лиями и затратой большого количества времени. При создании некоторых его частей, особенно при разработке сканера и про- цессора грамматического разбора, можно большую часть рабо- ты проделать автоматически. Компилятор компиляторов — это программное средство, используемое для облегчения разработки компиляторов. Такого рода средства часто также называют ге- нераторами компиляторов или системами построения трансля- торов. Процесс; использования типичного компилятора компилято- ров-иллюстрируется на рис. 5.28. Пользователь (т. е. разработ- чик компилятора) предоставляет описайие транслируемого язы- ка. Это описание может состоять из набора лексических пра- вил,.определяющих лексемы, и грамматики исходного языкй. Некоторые компиляторы компиляторов используют эту инфор-
298 Гл. 5. Компиляторы мацию для генерации программ, осуществляющих сканирование и грамматический разбор. Другие компиляторы компиляторов создают таблицы, необходимые для работы стандартных про- грамм осуществления лексического и синтаксического анализа, поставляемых вместе с компилятором компиляторов. В дополнение к описанию исходного языка пользователь предоставляет также ряд семантических программ генерации кода. Часто каждая семантическая программа соответствует не- которому семантическому правилу грамматики, как это обсуж- далось в разд. 5.1. Эти программы вызываются в процессе Рис. 5.28. Автоматизация создания компилятора при использовании компи- лятора компиляторов. грамматического разбора, как только распознана конструкция языка, описанная соответствующим правилом грамматики. Дру- гие компиляторы компиляторов могут осуществлять между об- ращениями к семантическим программам граматический раз- бор больших фрагментов исходной программы. В этом случае для разобранных предложений вводится некоторая специаль- ная форма записи, например в виде фрагмента дерева грамма- тического разбора, которая может быть передана семантиче- ским программам. Такой подход обычно используется при опти- мизации объектного кода. Компиляторы компиляторов часто предоставляют также специальные языки, нотацию, структуры данных и другую информацию, которая может быть использо- вана при написании семантических программ. Основной выигрыш при использовании компилятора компи- ляторов состоит в облегчении процесса разработки и тестирова- ния компилятора. Однако объем работы, который необходимо проделать пользователю, существенно различается для разных систем в зависимости от гибкости предоставляемых пользовате- лю средств. Сгенерированные компиляторы обычно требуют больше памяти и компилируют более медленные программы, чем компиляторы, написанные вручную. Однако генерируемый ком- пилятором объектный код может оказаться лучше при исполь- зовании компилятора компиляторов. В результате автоматиза-
5.5. Примеры реализаций 299 ции разработки сканера, программ грамматического разбора и использования некоторых специальных средств, предоставляв*, мых для написания семантических программ разработчик компн* лятора освобождается от множества технических деталей, свя- занных с его разработкой. В результате он может уделить боль- ше внимания процедурам генерации и оптимизации объектного кода. Краткое описание одного из компилятора компиляторов (YACC) приведено в разд. 5.5.4. Дальнейшее обсуждение и при- меры компиляторов компиляторов содержатся в работах Грис [1971] и Хопгуд [1959], 5.5. Примеры реализации В этом разделе мы кратко опишем устройство ряда компи- ляторов. В разд. 5.5.1 описывается компилятор ETH Pascal (компилятор с Паскаля, разработанный в Высшей политехни- ческой школе в Цюрихе), который широко распространен на машинах серии CYBER фирмы CDC. В разд. 5.5.2 описыва- ется компилятор UCSD Pascal (UCSD — University of Cali- fornia San Diego) — один из лучших компиляторов на Р-код. Оба эти компилятора являются однопросмотровыми и практи- чески не осуществляют оптимизацию объектного кода. В разд. 5.5.3 описан компилятор IBM FORTRAN Н, предна- значенный для использования на машинах серии System/370 фирмы IBM. Это многопросмотровый компилятор, позволяющий Получить высокооптимизированный объектный код. Разд. 5.5.4 содержит описание компилятора компиляторов YACC, разработанного в Bell Laboratories для использования в рамках операционной системы UNIX. Мы также кратко опи- шем генератор сканеров LEX, обычно используемый вместе с YACC. Так же как и в наших предыдущих описаниях реальных программных систем, мы не будем пытаться дать полного опи- сания какого-либо из этих компиляторов. Для читателей, же- лающих получить более подробную информацию, приведены не- обходимые ссылки. 5.5.1. Компилятор ETH Pascal0 Язык программирования Паскаль был создан Никлаусом Виртом во время его работы в Высшей политехнической школе } Использованы материалы из «The Zurich Implementation Amman>4 Pascal: The Language and its Iftiplenlentation, copyright 1981 by John Wiley and Sons, Перепечатано с разрешения.
300 Гл. 5. Компиляторы !(ЕТН) в Цюрихе в 1968—1970 гг. Первый компилятор с Пас- каля был написан в 1970 г.. Полученный опыт привел к появ- лению новой версии Паскаля в 1972 г. и разработке для нее компилятора в 1972—1974 гг. Этот компилятор используется в настоящее время более чем в 150 организациях, расположенных в разных частях света, и количество его пользователей непре- рывно растет. Именно этот второй компилятор мы и опишем в данном разделе. Более подробное описание содержится в ра- боте Амманн [1981]. Компилятор с Паскаля, разработанный в ЕТН в Цюрихе (далее просто «компилятор ЕТН»), является однопросмотровым компилятором, генерирующим коды для ЭВМ CDC 6000 и ЭВМ серии CYBER. Он является самокомпи- лятором 9 в том смысле, что сам он написан на том языке, ко- торый компилирует. Одно из удобств такого подхода состоит в возможности переноса компилятора на новую машину. Исход- ная версия компилятора, предназначенная для работы на ма- шине А, модифицируется с тем, чтобы порождать объектный код, цредназначенный для машины В. Если эту версию компи- лятора использовать для компиляции самого себя, то в резуль- тате мы получим компилятор, предназначенный для работы на машине В. Другое преимущество самокомпилятора состоит в образовании обратной связи, улучшающей процедуры генерации кода. Если компилятор будет модифицирован для получения более эффективного объектного кода и далее будет использо- ван для компиляции самого себя, то в результате мы получим более эффективный компилятор. Подобные самокомпиляторы довольно широко используются. Так получилось, что два дру- гих описанных в разд. 5.5 компилятора также являются само- компиляторами. Компилятор ЕТН содержит лексический анализатор, подоб- ный описанному нами в предыдущих разделах. Этот анализатор использует таблицу зарезервированных идентификаторов, рас- положенных в виде линейного списка в порядке увеличения их длины, что облегчает идентификацию лексем. Синтаксический анализ осуществляется на основе метода рекурсивного спуска. Таблица имен компилятора организована в виде двоичного дерева. Память, необходимая для таблицы имен и для других используемых структур данных, для более эффективного ее ис- пользования выделяется динамически. После окончания компи- ляции отдельной процедуры соответствующий ей фрагмент таб- лицы имен становится более ненужным. Следовательно, отве- денную для него память можно освободить для дальнейшего использования. 1) В нашей литературе этот термин редко употребляется, чаще говорят, о раскрутке или самораскрутке. — Прим, перев.
5.5. Примеры реализации 301 Во время выполнения программы необходимая для хранен ния данных память выделяется динамически с использованием метода автоматического распределения памяти, описанного в разд. 5.3.1. Области инициализации порождаются и записыва- ются в стек при вызове каждой процедуры. Эти области связа- ны между собой в цепочку в порядке их появления в стеке, как это изображено на рис. 5.20. Существует также отдельная цепочка, содержащая только те области, переменные которых доступны для использования в данный момент в соответствии с правилами видимости имен в Паскале. Эта цепочка исполь- зуется для тех же целей, что и дисплей, который мы рас- сматривали в разд. 5.3.4 (см. рис. 5.26). Чтобы улучшить генерируемый код, компилятор следит за содержимым всех 24 регистров ЭВМ серии CYBER во время выполнения программы. Если в процессе генерации кода требу- ется некоторый элемент данных или указатель на определенный адрес, то прежде всего проверяется содержимое этих регист- ров. Если требуемое значение или указатель уже находится на регистре, то не требуются никакие операции загрузки. Когда необходимо загрузить на регистр X некоторый элемент данных, компилятор ищет свободный регистр. Если в наличии нет ни одного свободного регистра, компилятор выбирает тот регистр, значение которого должно быть заменено. Этот выбор основан на времени, которое прошло с момента последнего использова- ния содержимого регистра, а также на типе значения, содер- жащегося на регистре. Замена константы более предпочтитель- на, чем замена значения переменной, поскольку константу легче перезагрузить на регистр, когда это понадобится. Используемый метод выбора нужного регистра представляется весьма эффек- тивным. Когда компилятор ЕТН был использован для компи- ляции самого себя, результирующий объектный код был примерно на 30 % меньше кода, порожденного предыдущей версией компилятора, который не отслеживал содержимое ре- гистров. Компилятор ЕТН генерирует перемещаемый объектный код в формате, совместимом с имеющимся в системе связывающим загрузчиком. Все ссылки вперед внутри компилируемой про- цедуры окончательно определяются до того, как компилятор за- писывает объектную программу на внешнюю память. Весь объ- ектный код, соответствующий компилируемой процедуре, хранится в оперативной памяти до тех пор, пока не будет за- вершена компиляция этой процедуры. Для этого динамически выделяется необходимая память. После завершения процесса компиляции процедуры соответствующий объектный код запи- сывается на внешнюю память и выделенная для его хранения оперативная память освобождается.
302 Гл. 5. Компиляторы 5.5.2. Компилятор UCSD Pascal Система UCSD Pascal представляет собой законченную си- стему программирования, предназначенную для небольших компьютеров и предоставляющую средства для разработки и выполнения программ. Программы, разработанные в рамках этой системы, могут работать на большом количестве машин различных марок. В этом разделе мы опишем компилятор UCSD Pascal. В разд. 6.5 содержится краткое обсуждение всей системы UCSD Pascal. Компилятор UCSD Pascal был разра- ботан на основе компилятора на Р-код, разработанного в Цю- рихе, который в свою очередь является модифицированной версией компилятора ЕТН, описанного в разд. 5.5.1. Соответ- ствующее описание содержится в работе Нори [1981]. Компи- лятор UCSD Pascal является однопросмотровым компилятором, написанным на Паскале, генерирующем Р-код для псевдомаши- ны (P-машины). Входным языком компилятора является не- сколько расширенный стандартный Паскаль. P-машина для UCSD Pascal имеет стековую архитектуру и поддерживает ряд структур данных и команд высокого уровня. Регистры общего назначения на P-машине отсутствуют. Для большинства арифметических, логических и других операций соответствующие аргументы находятся в стеке. Стек также ис- пользуется для запоминания параметров и справочной инфор- мации о вызовах процедур и функций. Имеются несколько рё- гистров специального назначения, которые используются опера- ционной системой и интерпретатором Р-кода. Например, такими регистрами являются счетчик команд и указатель на теку- щую область инициализации. Конструкция P-машины была разработана на основании ана- лиза объектного кода, получаемого при компиляции с Паскаля. Была сделана попытка выделить наиболее часто встречающиеся последовательности команд, которые занимают основное мес- то в объектной программе, и предусмотреть необходимую под- держку этих действий в P-коде. Одна инструкция Р-кода может обратиться к элементу данных, расположенному как в текущей, так и в любой другой области инициализации, связанной с те- кущей процедурой. Отдельные инструкции Р-кода позволяют вы- числять индексы по массиву, выполнять операции над массива- ми и строками, выполнять даже такие операции, как объедине- ние и пересечение множеств. Существуют также инструкции, осуществляющие необходимые действия при вызове процедур. Одна операция P-машины может вызывать такие действия, как образование области инициализации, модификация некоторых структур данных и операции поддержки соответствия всех этих структур. Благодаря наличию таких специальных команд объ- ектная программа в P-коде обычно намного короче эквивалент-*
5.5. Примеры реализации 303 ной объектной программы для машины с более традиционной архитектурой. Компилятор UCSD Pascal предоставляет программисту воз- можность непосредственно использовать команды P-кода внут- ри программы на Паскале. Эта возможность бывает полезна в некоторых специальных случаях (например, при разработке си- стемных программ нижнего уровня). Имеется также возмож- ность генерации непосредственно машинного кода для задан- ного компьютера, используя дополнительный этап в процессе компиляции. Генератор «местного кода» получает на входе за- конченную программу на P-коде, сгенерированную компилято- ром, и транслирует заданные процедуры этой программы из P-кода в объектный код заданного компьютера. Оттранслиро- ванная в машинные коды программа занимает больше памяти, чем соответствующий ей P-код; однако время работы оттранс- лированных процедур может уменьшиться в 10 и более раз, по сравнению со временем интерпретации Р-кода. Более подробное описание компилятора UCSD Pascal и P-машины можно найти в работах SofTech Microsystems [1983] и Овергаард [1980]. 5.5.3. Компилятор Fortran Н фирмы IBM Компилятор Fortran Н был разработан для использования на машинах фирмы IBM серий 360 и 370. Входным языком компилятора является Фортран IV; на выходе компилятора фор- мируется объектный модуль, который может быть обработан редактором связей системы. Одна из основных целей, которая ставилась при разработке этого компилятора,— получить эф- фективный объектный код. Пользователь может выбрать один из трех уровней оптимизации при каждом обращении к компи- лятору. Компилятор Fortran Н состоит из системного диспетчера, управляющего процессом компиляции, четырех логических фаз компиляции (обозначаемых как фазы 10, 15, 20 и 25) и фазы обработки ошибок (фаза 30). Конкретные действия, осущест- вляемые на каждой фазе компиляции, и требуемое число фаз зависят от выбранного уровня оптимизации. Компилятор пред- ставляет собой оверлейную программу, разбитую на 13 сегмен- тов. Корневым сегментом оверлейной структуры является си- стемный диспетчер. Все остальные сегменты представляют со- бой либо фазу целиком, либо логически завершенную часть ка- кой-либо фазы. Системный диспетчер осуществляет инициализацию компи- лятора, вызывает различные фазы для выполнения и распреде- ляет необходимую для работы компилятора память. Се акже принимает запросы на ввод-вывод, идущие из дг;гкл фаз
304 Гл. 5. Компиляторы компилятора, и передает их операционной системе для выполз нения. Фаза 10 читает исходную программу и осуществляет лекси- ческий анализ. На этой фазе заполняются необходимые инфор- мационные таблицы для всех переменных, констант, номеров предложений, находящихся в исходной программе. Выходом фазы 10 является последовательность пар оператор — операнд в порядке их следования в исходной программе. В данном кон- тексте термин оператор включает в себя такие элементы, как скобки и запятые, в дополнение к обычным арифметическим, логическим и родственным им операциям. Под термином опе- ранд понимаются переменные, константы и литералы. Кроме лексического анализа на фазе 10 осуществляются также печать листинга исходной программы и печать таблицы ссылок, если этого потребовал программист. Фаза 15 разделена на 2 части. Первая часть транслирует в последовательность четверок то, что получено на выходе фазы !10. Для анализа предложений исходной программы использу- ется метод операторного предшествования. Если требуется оптимизация объектного кода, на фазе 15 также осуществля- ются разбиение программы на линейные участки и сбор инфор- мации об операторах перехода и использовании констант и переменных в каждом участке. Вторая часть фазы 15 опреде- ляет относительные адреса для хранения констант и их пере- менных. Она также определяет адресные константы, необходи- мые для адресации этих элементов данных. На фазе 20 реализуются различные методы оптимизации объектного кода. Выполняемые действия зависят от уровня оп7 химизации, выбранного программистом. Если оптимизации во- обще не требуется, то на фазе 20 осуществляется выделение регистров для операндов четверок. При этом, однако, не дела- ется попытки максимально использовать все имеющиеся регист- ры и не предпринимается попыток оставить значения операндов ца регистрах для дальнейшего использования. Эти действия на- рываются базовым распределением регистров и осуществляются Ра один просмотр программы. Если требуется первый уровень оптимизации, то на фазе 20 осуществляется полное распределение регистров. Его назначе- ние аналогично базовому распределению регистров, при этом, однако, более полно используются имеющиеся регистры и де- лается попытка сохранить значения операндов на регистрах для последующего использования. Делается попытка также сохра- нить на регистрах в течение всего времени выполнения объект- ной программы наиболее часто используемые операнды. Пол- ное распределение регистров требует нескольких просмотров Промежуточной формы представления программы. На фазе 20 Осуществлятся также оптимизация инструкций перехода, что-
5.5. Примеры реализации 305 бы избежать ненужной загрузки регистров адресами пе- реходов. Если задан высший уровень оптимизации, то на фазе 20 также осуществляется оптимизация циклов. Прежде всего опре- деляется структура программы в терминах находящихся в ней циклов и порядок, в котором эти циклы выполняются. Далее реализуются такие методы оптимизации, как удаление общих подвыражений и удаление инвариантов цикла. На высшем уров- не оптимизации на фазе 20 также осуществляется только что описанное полное распределение регистров и оптимизация пе- реходов. На фазе 25 генерируется объектный код на основании ин- формации, порожденной предыдущими фазами. Фаза 30 вызы- вается после окончания фазы 25, только если на предыдущих фазах обнаружены какие-либо ошибки. Назначением этой фазы является выдача соответствующих диагностических сообщений. Дальнейшая информация о компиляторе Fortran Н содер- жится в IBM [19726]. 5.5.4. Компилятор компиляторов YACC Компилятор компиляторов YACC (Yet Another Compiler- Compiler — еще один компилятор компиляторов) является гене- ратором программ грамматического разбора, который исполь- зуется в рамках операционной системы UNIX. Этот компилятор компиляторов использовался для разработки компиляторов с языков Паскаль, Ratfor, АПЛ, Си и ряда других языков про- граммирования. Он также использовался для менее традицион- ных приложений, включающих, например, язык определения типов и систему управления документами. В этом разделе мы дадим краткое описание компилятора компиляторов YACC и программы LEX — генератора программ лексического анализа, связанного с YACC. Дальнейшая информация об этих про- граммных средствах содержится в Джонсон [1980], Джонсон [[1975] и Леек [1975]. Компилятор компиляторов YACC используется вместе с со- ответствующим лексическим сканером. Этот сканер вызывается в процессе грамматического разбора каждый раз, когда требу- ется очередная лексема. Сканер каждой лексеме ставит в со- ответствие целое число, определяющее тип этой лексемы, как это было описано в разд. 5.1. Сканер также формирует табли- цу имен обработанных идентификаторов. Подсистема LEX является генератором сканеров. Она может быть использована для порождения программ сканеров требуе- »«>го для YACC типа. Фрагмент спецификаций, подаваемый на вход системы LEX, изображен на рис. 5.29а. Каждая строка левого столбца представляет собой шаблон, с которым должны
306 Гл. 5. Компиляторы сравниваться фрагменты входного потока. Если сравнение за- кончилось успешно, то вызывается программа соответствующей строки правого столбца. Эти программы написаны на языке программирования Си. Результатом работы этих программ обычно является очередная распознанная лексема. Кроме того, они заполняют необходимые таблицы и выполняют другие сход- ные действия. В примере на рис. 5.29а первому шаблону не соответствуют никакие действия; его смысл состоит в том, чтобы удалить * * ; /# игнорирование пробелов */ let return(LET); * ** return(MUL); * =* return(ASSIGN); la-zA-Zl Ca-zA-Z0-91* Сзанести в таблицу) return (ID); а Покеп ASSIGN ID LET MUL ... statement 5 LET ID ASSIGN expr < ... ) expr s expr MUL expr < $$ « build(MUL41,$3);> expr s ID C ... 1 6 Рис. 5.29. Пример входных спецификаций для LEX и YACC. пробелы из входного потока. Программы, соответствующие сле- дующим трем шаблонам, должны сформировать на выходе тип соответствующей лексемы: тип LET для ключевого слова let, MUL для оператора * и ASSIGN для оператора =. Как уже говорилось, внутренним представлением для LET, MUL и дру- гих лексем являются целые числа. Пятый шаблон определяет структуру распознаваемых идентификаторов. Первый символ должен находиться в диапазонах а — z или А — Z. За ним мо- жет следовать произвольное количество символов из диапазо- нов а—z, А—Z или 0—9. Символ * в этом шаблоне указывает на то, что допускается произвольное количество повторений терма, предшествующего *. Программа этого шаблона должна заполнить необходимые для описания найденного идентифика-
5.5. Примеры реализации 307 тора строки соответствующих таблиц и сформировать на выходе тип лексемы ID. В соответствии со спецификациями, приведенными на рис. 5.29а, входная строка let x = y*z будет представлена в качестве последовательности лексем: LET ID ASSIGN ID MUL ID Обратите внимание, что выбирается именно первый совпавший шаблон, в результате чего ключевое слово let распознается как лексема LET, а не как идентификатор ID. LEX может использоваться для создания весьма сложных сканеров. Однако для некоторых языков, таких как Фортран, лексические анализаторы все еще должны создаваться вруч- ную. В качестве входной информации генератору процессоров грамматического разбора системы YACC подается грамматика языка, для которого создается компилятор, и набор требуемых действий, соответствующих правилам используемой грамматики. Фрагмент таких входных спецификаций представлен на рис. 5.296. Первая строка представляет собой описание типов используемых лексем. Другие строки являются правилами грам- матики. Грамматический процессор системы YACC вызывает связанную с каждым правилом семантическую программу, как только распознана соответствующая конструкция языка. Каж- дая такая программа может передать в другие программы не- которое значение, присвоив его переменной $$. Эти значения, порожденные ранее проработавшими семантическими програм- мами или сканером, хранятся в переменных $1, $2, и т. д. Они определяют результаты работы программ, соответствующих компонентам правой части правил грамматики, упорядоченных слева направо. Пример использования этих переменных приведен на рис. 5.296. Семантическая программа, связанная с правилом ехрг : ехрг MUL ехрг строит фрагмент дерева грамматического разбора входного предложения, используя порождающую функцию build. Постро- енный фрагмент дерева присваивается на выходе из семантиче- ской программы переменной $$. Аргументами порождающей функции являются оператор MUL и значения (являющиеся фрагментами дерева грамматического разбора), порожденные после распознавания операндов. Эти значения обозначены $1 и $.3 В некоторых случаях полезно вызывать семантические про- граммы после распознавания каждого компонента грамматиче-
308 Гл. 5. Компиляторы ского правила. Система YACC позволяет это делать, допуская вызов семантических программ не только по окончанию каждо- го правила, но и в середине анализа. Значения, порожденные такими программами, доступны всем остальным программам, которые будут вызваны в процессе анализа данного правила. Пользователь может также определить глобальные переменные, которые могут использоваться всеми семантическими програм- мами и лексическим сканером. Порожденные системой YACC процессоры грамматического разбора используют метод грамматического разбора снизу вверх, называемый LALR(l). Этот метод годится для большого класса наиболее интересных грамматик. В частности, нет необ- ходимости избегать левой рекурсии. Возможно использование даже неоднозначных грамматик, за счет введения дополнитель- ных грамматических правил, устраняющих двусмысленность. Порожденные YACC процессоры имеют очень хорошую систе- му обнаружения и диагностики ошибок. Она позволяет повтор- но проанализировать фрагмент, в котором обнаружена ошибка, либо продолжить обработку входного потока, пропустив оши- бочный фрагмент. Упражнения Раздел 5.1 1. Постройте по грамматике на рис. 5.2 дерево грамматического разбора для следующих конструкций <id—list): a) ALPHA б) ALPHA,BETA,GAMMA 2. Постройте по грамматике на рис. 5.2 дерево грамматического разбора для следующих конструкций <ехр>: a) ALPHA + ВЕТА б) ALPHA - ВЕТА * GAMMA в) ALPHA DIV (BETA + GAMMA)-DELTA 3. Предположим, что правила 10 и 11 грамматики на рис. 5.2 измене- ны на (ехр) ::= (term) | (ехр) * (term) | (ехр) DIV (term) (term) ::= (factor) | (term) + (factor) | (term) — (factor) Постройте деревья грамматического разбора для конструкций <ехр> упр. 2 в соответствии с такой модифицированной грамматикой. Каким образом из- менения в грамматике повлияли на отношение предшествования для ариф- метических операторов? 4. Предположим, что правила 10 и 11 грамматики на рис. 5.2 заме- нены на одно правило: (ехр) ::= (factor) | (ехр) 4- (factor) | (ехр) — — (factor) ] (ехр) * (factor) | (ехр) DIV (factor)
Упражнения 309 Постройте деревья грамматического разбора для конструкций <ехр> упр. 2 по такой грамматике. Какие при этом возникают проблемы? . 5. Модифицируйте грамматику на рис. 5.2 с тем, чтобы включить опе- рацию возведения в степень в форме XfY. Не забудьте о том, что возве- дение в степень должно иметь более высокий приоритет, чем остальные арифметические операции. 6. Модифицируйте грамматику на рис. 5.2 с тем, чтобы включить пред- ложения вида IF условие THEN предложение— 1 ELSE предложение — 2 где конструкция ELSE может быть опущена. Вы можете предположить, что условие имеет форму а < b, а> b или а — Ь. Здесь а и b — иденти- фикаторы или целые числа. Вы можете не заботиться о вложенных струк- турах IF, т. е. предложение—1 и предложение — 2 не могут быть IF пред- ложениями. 7. Модифицируйте грамматику на рис. 5.2 так, чтобы список вывода для оператора WRITE наряду с идентификаторами мог содержать заклю- ченные в кавычки строки символов. 8. Составьте алгоритм, просматривающий входной поток и распознаю- щий идентификаторы и операторы. Идентификатор может иметь длину до 10 символов. Он должен начинаться с буквы, а остальные символы, если они есть, должны быть буквами или цифрами. Необходимо распо- знавать следующие операторы: +, —, *, DIV и :==. Ваш алгоритм должен на выходе образовывать целое число, определяющее тип' распознанной лек- семы в соответствии с кодировкой, принятой на рис. 5.5. Если будет обна- ружена недопустимая комбинация символов, то алгоритм должен образовы- вать число —1. 9. Модифицируйте написанный вами в упр. 8 сканер так, чтобы он наряду с идентификаторами распознавал и целые числа. Целые могут на- чинаться со знака (+ или —), но не могут начинаться с цифры 0. 10. Выберите какой-либо из знакомых вам языков программирования высокого уровня и напишите для него лексический сканер. 11. Осуществите грамматический разбор следующих предложений про- граммы на рис. 5.1, используя метод операторного предшествования и мат- рицу предшествования на рис. 5.7: а) предложение присваивания в строке 11; б) описание в строке 3; в) предложение FOR, начинающееся в строке 7. 12. Осуществите грамматический разбор всей программы на рис. 5.1, Используя метод операторного предшествования и матрицу предшествования на рис. 5.7. 13. Осуществите грамматический разбор предложения присваивания в строке И на рис. 5.1, используя метод рекурсивного спуска и процедуры, приведенные на рис. 5.11. 14. Напишите процедуры метода рекурсивного спуска, соответствующие правилам для <dec—list>, <dec> и <type> на рис. 5.9. ИсНользуйте эти про- цедуры для разбора описаний в строке 3 на рис. 5.1. 15. Напишите процедуры метода рекурсивного спуска для других нетер- миналов грамматики на рис. 5.9. Осуществите грамматический разбор всей программы на рис. 5.1 методом рекурсивного спуска. 16. Используйте программы на рис. 5.12—5.14 для генерации кода, со- ответствующего следующим предложениям программы на рис, 5.11 А) предложение присваивания в строке 11; о) предложение WRITE в строке 15;
310 Гл. 5. Компиляторы в) предложение FOR, начинающееся в строке 7. Воспользуйтесь при этом деревом грамматического разбора на рис. 5.4 для определения порядка, в котором грамматический процессор распознает раз- личные конструкции, содержащиеся в этих предложениях. 17. Используйте программы на рис. 5.12—5.14 для генерации кода всей программы на рис. 5.1. 18. Напишите процедуры генерации объектного кода для новых правил, которые вы добавили к грамматике для определения предложений IF. 19. Предположим, что грамматика на рис. 5.2 расширена путем введе- ния переменных с плавающей точкой (<type> REAL) в дополнение к целым переменным. Как при этом изменятся приведенные в тексте программы ге- нерации кода? Считайте, что допустимы смешанные арифметические вы- ражения, соответствующие обычным правилам Паскаля. 20. Приведенные в тексте программы генерации объектного кода ис« пользуют непосредственную адресацию для обращения к целым константам, используемым в арифметических выражениях (например, число 100 в выра* жении SUM DIV 100). Каким образом такие константы могут обрабатываться компилятором для машины, не имеющей непосредственной адресации? 21. Какого типа ошибки в исходной программе могут быть обнаружены во время лексического анализа? 22. Какого типа ошибки в исходной программе могут быть обнаружены во время синтаксического анализа? 23. Какого типа ошибки в исходной программе могут быть обнаружены во время генерации кода? 24. В чем таблица символов, используемая компилятором, может от- личаться от подобной таблицы ассемблера? Раздел 5.2 1. Перепишите приведенные на рис. 5.12 и 5.13 программы генерации объектного кода так, чтобы они порождали не объектный код, а четверки. 2. Напишите программы генерации объектного кода из четверок, полу- ченных программами упр. 1. (Наверное, вам для этого понадобится про- грамма, сходная по функциям с процедурой GETA на рис. 5.13.) 3. Воспользуйтесь программами из упр. 1 для получения четверок, со- ответствующих следующему фрагменту программы: READ(X,Y); Z := 3*Х- 5 * V + X * Y; 4. Используйте программы из упр. 2 для получения объектного кода из четверок, полученных в упр. 3. 5. Перепишите приведенные на рис. 5.14 программы генерации объект- ного кода так, чтобы они порождали не объектный код, а четверки. 6. Используйте программы из упр. 1 и 5 для получения четверок, со- ответствующих программе на рис. 5.1. 7. Разбейте полученные в упр. 6 четверки на линейные участки и по- стройте блок-схему программы. 8. Предположим, что вы должны сгенерировать объектный код для УУМ/ДС из четверок, полученных в упр. 6. Предложите способ распреде- ления регистров для оптимизации объектного кода, который бы использовал регистры S и Т для хранения значений переменных и промежуточных ре- зультатов.
Упражнения 311 Раздел 5.3 1. Составьте алгоритм работы пролога процедуры в предположении, что область инициализации имеет представленный на рис. 5.20 формат. 2. Составьте алгоритм работы эпилога процедуры в предположении, что область инициализации имеет представленный на рис. 5.20 формат. 3. Предложите способ использования стека областей инициализации для осуществления динамического распределения памяти. Каковы преимущества и недостатки этого способа по сравнению с использованием независимой области свободной памяти? 4. Предположим, что массив описан следующим образом: С : ARRAY[5. .20] OF INTEGER Сгенерируйте четверки для предложения С[1] := 0 5. Предположим, что массив описан следующим образом: D: ARRAY[—10.. 10,2.. 12] OF INTEGER Сгенерируйте четверки для предложения D[I,J] := 0 6. Обобщите предложенные в разд. 5.3.2 методы для хранения трех- мерных массивов по строкам. Для массива, описанного в виде Е : ARRAY[1. .5,1.. 10,0. .8] OF INTEGER сгенерируйте четверки для предложения; E[I, J, К]: = 0. 7. Как надо изменить базовый адрес массива А, определенный на рис. 5.22а, чтобы избежать необходимости вычитать 1 из значения индекса (четверка 1)? 8. Каким образом способ из упр. 7 можно обобщить на двумерные массивы? 9. Пусть массив Т описан как Т: ARRAY[1. .5,1.. 100] OF INTEGER Оттранслируйте следующие предложения в четверки и исключите общие подвыражения. К := J - 1; FOR I := 1 ТО 5 DO BEGIN T[I,J] := К * К; J := J + K; ТГТ,J] := к* К — 1; END 10. Модифицируйте четверки из упр. 9 для исключения инвариантов цикла. 11. Составьте алгоритм порождения необходимого дисплея при вызове процедуры. Ваш алгоритм может использовать предыдущее состояние дис- плея (т. е. состояние до вызова процедуры), адрес области инициализации, порожденной для вызываемой процедуры, и уровень вложенности вызывае- мой процедуры.
Глава 6. Операционные системы В этой главе будут рассмотрены функции операционных си- стем и способы их разработки. Не пытаясь в единственной гла- ве дать исчерпывающее изложение темы, явившейся предметом многих книг, мы коснемся только наиболее важных идей и ре- шений, иллюстрируя их примерами и снабжая ссылками на со- ответствующую литературу. Способы разработки операционных систем и их назначение достаточно разнообразны. Одни, очень простые, предназначены для обеспечения работы единственного пользователя на персо- нальной ЭВМ. Другие, крайне сложные системы, дают возмож- ность одновременной работы многих пользователей, управляют высокоразвитыми аппаратными и программными средствами. В разд. 6.1 рассмотрены основные свойства операционных си- стем, которые присущи практически любому их элементу. Из- за большого разнообразия операционных систем перечень этих свойств необычайно сжат. Он состоит лишь из нескольких об- щих функций, которые могут быть взяты едва ли не как опре- деления термина операционная система. Раздел 6.2 содержит описание некоторых важных свойств машинно-зависимых операционных систем, а разд. 6.3 освещает характеристики машинно-независимых реализаций. Многие из рассматриваемых в этих разделах функций (к ним относятся, например, распределение системных ресурсов и управление связью между различными пользователями) должны быть ре- ализованы практически в любой операционной системе, поддер- живающей одновременную работу многих пользователей. В разд. 6.4 коротко излагаются некоторые альтернативные способы построения операционных систем. В разд. 6.5 описыва- ются несколько реальных операционных систем, дающих пред- ставление лишь об отдельных формах и функциях подобного программного обеспечения. 6.1. Основные функции операционных систем Ниже вкратце рассматриваются основные функции, обычно выполняемые всеми операционными системами. Главная задача операционной системы — упростить общение пользователей е
6.1. Основные функции операционных систем . 313 ЭВМ1). Системное программное обеспечение является над- стройкой над базовыми аппаратными средствами и делает ра- боту пользователя с машиной более удобной. Например, обес- печивая максимальную производительность ЭВМ, операционная система осуществляет достаточно сложный процесс управ- ления ее ресурсами, все нюансы которого скрыты от пользо- вателя. Рис. 6.1. Основная концепция операционной системы. По сравнению с предыдущими главами приводимое ниже из- ложение основных свойств намного короче и носит более об- щий характер. Например, для ассемблеров, которые рассмотре- ны в гл. 2, нам удалось найти общую структуру, не зависящую от машинной реализации. Однако операционные системы для персонального компьютера и для суперЭВМ с большим числом пользователей, за исключением основных подходов, будут силь- но различаться. Основные функции операционных систем могут быть изо- бражены, как показано на рис. 6.1. Взаимодействие с програм- мистами, операторами и т. д. осуществляется через интерфейс пользователя, который поддерживается операционной системой. 1) Не , менее важной задачей операционной системы является создание удобного интерфейса не только с пользователем, но и с такими элементами системного программного обеспечения, как трансляторы, загрузчики, мони- торы.—- Прим. ред.
314 Гл. 6. Операционные системы Именно его мы имеем в виду при ответе на вопрос: «Какого характера операционная система?». Если интерфейс предусма- тривает наличие некоторого языка управления, то, например, запуск программы на счет может быть осуществлен по коман- де RUN Р. В разд. 6.1.1 приводится терминология, связанная с операционными системами, и дается их классификация, осно- ванная на предоставляемом ими интерфейсе пользователя. В разд. 6.1.2 коротко описываются некоторые возможные функ- ции интерфейса пользователя. Для выполнения часто встречающихся задач операционные системы предоставляют программам определенный набор услуг. Например, для чтения из файла некоторого набора данных программа Р может использовать стандартную сервисную про- грамму. Последняя вызывается командой типа read(f), с по- мощью которой задается и имя файла. Всю заботу об осуще- ствлении ввода-вывода, производимого на машинном уровне, возьмет на себя операционная система. Сервисные программы могут рассматриваться как часть операционного окружения задач, находящихся в решении. О нем пойдет речь в разд. 6.1.3. Более подробно некоторые сервисные функции и стандартные программы описаны в разд. 6.2 и 6.3. В данной главе предполагается, что функции операционной системы реализованы только программным обеспечением. Од- нако многие из них могут быть представлены программно-аппа- ратными средствами (firmware), состоящими из набора микро- программ. Дополнительную информацию об этом можно полу- чить в Дейтел [1984]. 6.1.1. Типы операционных систем Очень часто способы классификации операционных систем основываются на типе предоставляемого ими интерфейса поль- зователя. Многие понятия, связанные с операционными систе- мами, возникают из представления пользователей о системе. В данном разделе вводится терминология, наиболее часто ис- пользуемая для описания операционных систем. При этом не всегда удается достичь полной ясности в определении типов некоторых систем: они подпадают более чем под одну катего- рию и их классификации в некоторых пунктах совпадают. Один из способов классификации связан с количеством пользователей, одновременно обслуживаемых системой. Назо- вем однопрограммной систему, которая обеспечивает работу одного пользователя. Это самый старый тип операционных си- стем. Сейчас его можно встретить на микрокомпьютерах и пер- сональных ЭВМ. Вероятно, и на гипотетической машине УУМ из-за малого объема памяти и нехватки каналов (что сильно
6.1. Основные функции операционных систем 315 затрудняет обслуживание более одного пользователя) следует считать, что используется однопрограммная система. Мультипрограммная система позволяет одновременно выпол- нять несколько заданий пользователей, управляя при этом рас- пределением процессора между ними. Для того чтобы задания не мешали друг другу, операционная система создает для них соответствующее операционное окружение. Мультипроцессорная система схожа с мультипрограммной с той разницей, что в пер- вой возможно использование более чем одного ЦП. Основная цель мультипрограммирования — увеличение про- изводительности вычислительной системы за счет разделения ее ресурсов между несколькими заданиями. Например, одно зада- ние может занимать процессор, в то время как другое — ожи- дать завершения операции ввода-вывода (подробнее об этом см. в разд. 6.2). Другой способ классификации операционных систем основан на типе доступа, предоставляемого интерфейсом пользователя. В случае систем с пакетной обработкой в качестве задания вы- ступает последовательность управляющих операторов, записан- ная на машинных носителях (например, на перфокартах или диске). За исключением смены дисков или лент, осуществляе- мой операторами, всю заботу о считывании и выполнении зада- ний берут на себя операционные системы. Порядок, в котором выполняются задания, может быть выбран несколькими спосо- бами. Вопросы планирования рассматриваются в разд. 6.3. 'Диалоговый или интерактивный доступ некоторого числа поль- зователей обеспечивается системами разделения времени. Опе- рационная система исполняет директивы пользователей по мере того, как они вводятся, стараясь дать ответ на каждую коман- ду пользователя за разумно короткое время. Для обработки внешних сигналов, поступающих, например, с различных дат- чиков, и быстрого ответа на них используются системы реаль- ного времени. К ним относятся операционные системы, работа- ющие на электронно-вычислительных машинах, управляющих процессами, в которых время является критическим парамет- ром (например, ядерная реакция или полет космического ко-' рабля). Вообще говоря, мультипрограммные системы пакетной обра- ботки призваны сделать использование ЭВМ более эффектив- ным. Основной задачей систем разделения времени должно счи- таться обеспечение хорошего времени ответа пользователям, работающим в диалоговом режиме. Возможно, при этом при- дется примириться с использованием машины с меньшей эф- фективностью. Системы реального времени должны обеспечи- вать гарантированное время ответа на внешние события, для которых время является критическим параметром. Довольно часто все эти функции реализуются в одной системе. Напри-
316 Гл. 6. Операционные системы мер, многие системы пакетной обработки нередко поддержива- ют интерактивный режим, а другие вдобавок осуществляют й обслуживание процессов реального времени. Дополнительные сведения обо всех этих операционных си- стемах, равно как и подробности об их создании, можно найти в Дейтел [1984]. 6.1.2. Интерфейс пользователя Интерфейс пользователя, предоставляемый операционной си- стемой, предназначен для обеспечения нужд различных групп людей, имеющих дело с ЭВМ. Например, для работы на пер- сональной ЭВМ с простой операционной системой пользователю предоставляется набор команд, посредством которых он может получать доступ к системным программам (трансляторам, ре- дакторам, загрузчикам), осуществлять управление внешними файлами. Подобный командный язык достаточно прост в ис- пользовании; обычно имеется возможность в диалоге с маши- ной получить подсказку или просмотреть меню, содержащее варианты команд. В более сложных системах может существовать несколько различных языков общения с операционной системой. Непро- фессиональным программистам предоставляется возможность ра- ботать с ЭВМ на простом языке директив (command language). Профессиональными программистами может применяться мощ- ный и сложный язык, часто называемый языком управления заданиями (job control language). Кроме того, обычно суще- ствует специальный язык, при помощи которого осуществляется взаимодействие операторов и ЭВМ. Такой интерфейс оператора позволяет запускать и останавливать задания, выяснять их со- стояние и состояние системных ресурсов, управлять внешними действиями. Например, оператору может быть сообщено о не- обходимости установить ленту или диск. Разработка интерфейса пользователя обычно не влечет за собой решения сложных технических задач. Между тем его со- здание чрезвычайно важно, так как он эксплуатируется боль- шинством пользователей. В идеале интерфейс пользователя дол- жен быть разработан таким образом, чтобы отвечать требова- ниям всевозможных типов пользователей и в то же время учитывать цели и задачи вычислительной системы. Дополни- тельные сведения об интерфейсе пользователя можно найти в работах Дейтел [1984] и Питерсон [1983]. Для поддержки интерфейса пользователя операционная си- стема должна иметь также стандартные сервисные программы. В случае персональной ЭВМ это могут быть программы для управления вводом с клавиатуры и выводом на дисплей, а в более сложных системах — средства сопряжения с работающие
6.1. Основные функции операционных систем 317 ми в режиме разделения времени удаленными терминалами, считывателями с перфокарт и печатающими устройствами. Воз- можно также наличие интерфейса между локальной системой и другими ЭВМ, объединенными в сеть. Как часть интерфейса оператора многие операционные системы ведут накопление ста- тистики активности системы; она может использоваться для анализа производительности и обнаружения ошибок. 6.1.3. Операционное окружение Одной из наиболее важных функций операционной системы является поддержка операционного окружения пользователь- ских задач. Оно состоит из ряда стандартных сервисных про- грамм, которые могут быть использованы в процессе выполне- ния задачи и предоставлять средства для управления ресурса- ми вычислительной системы, выделяя их пользователю по мере надобности. В качестве примера услуг, предоставляемых операционным окружением, рассмотрим функцию ввода-вывода. Почти все операционные системы имеют стандартные программы, помо- гающие в осуществлении таких операций. Предположим, что программа работает на УУМ под управлением однопрограмм- ной системы. Чтобы прочитать байт без помощи операционной системы, программа должна содержать цикл, в котором анали- зируется состояние устройства и выполняется команда RD (см. рис. 2.1). Обнаружение и исправление ошибок также возложено на саму программу. Наличие поддержки со стороны операционной системы силь- но облегчает задачу. Программа пользователя может просто инициализировать стандартную сервисную программу, задав устройство, которое должно быть использовано. За всеми нюан- сами, такими как опрос состояния и подсчет переданных бай- тов, проследит операционная система. Она же примет необхо- димые меры по исправлению ошибок. Стандартная сервисная программа, подобная описанной выше, может восприниматься как расширение базовой машины. Типичная операционная система содержит много подобных про- грамм. Вместе взятые, они составляют расширенную машину, которая и используется во время выполнения программы. Про- граммам не нужно опускаться до уровня базовых аппаратных средств, поскольку все функции и возможности предоставляет расширенная машина. Она проще в использовании, чем реаль- ная. Это касается, например, нюансов выполнения операций ввода-вывода. Есть и другие преимущества. Например, опера- ции ввода-вывода на расширенной машине менее подвержены ошибкам, чем на реальной, так как за обнаружением и исправ- лением ошибок следит операционная система.
818 Гл. 6. Операционные системы Иногда расширенную машину называют виртуальной. Одна- ко термин «виртуальная машина» может иметь и другой смысл. Это двоякое использование термина описывается в разд. 6.4. Операционное окружение мультипрограммных операционных систем содержит также программы, которые управляют ресур- сами ЭВМ, выделяя их по необходимости заданиям пользова- телей. Например, оперативная память распределяется между Заданиями, одновременно находящимися в решении, централь- ный процессор предоставляется заданиям согласно заранее вы- бранной стратегии. За исключением конкретных запросов опе- рационной системе заданиям пользователя нет необходимости иметь дело с управлением ресурсами. Благодаря операционно- му окружению каждое задание выполняется как бы на отдель- ной расширенной машине, хотя в действительности базовая машина может быть распределена между несколькими пользо- вателями. В некоторых системах программы пользователей могут вызы- вать функции операционной системы, обращаясь непосредствен- но к фиксированным областям памяти. В документации по опе- рационной системе для пользователя дается описание областей, предназначенных для данных, и входных точек вместе с их реальными адресами. Например, точка входа стандартной сер- висной программы ввода-вывода может находиться в памяти по адресу 238. После установки в регистрах требуемых пара- метров программа пользователя может инициализировать эту сервисную функцию командой JSUB 238. Иногда возможно наличие в памяти одной точки входа для всех сервисных про- грамм, нужный тип обслуживания может быть определен при помощи кода запроса. Способ запроса связи с операционной системой при помощи обращения к фиксированной области памяти используется в микрокомпьютерах и персональных ЭВМ. Однако этот метод часто не удобен и является источником ошибок; кроме того, он может предоставить пользователю возможность обойти сред- ства защиты, встроенные в операционную систему. В более развитых системах пользователи запрашивают функции опера- ционных систем в основном при помощи специальных машин- ных команд, таких как вызов супервизора (SVC — SunerVisnr Call). Выполнение команды SVC вызывает прерывание, в ре- зультате которого управление передается сервисной стандарт- ной программ^ операционной системы. Код, которым сопровож- дается команда SVC, определяет тип запроса. Обработка пре- рываний операционной системой рассматривается в разд. 6.2.1. Как правило, в машине любое прерывание вызывает перевод ЦП из' режима пользователя в режим супервизора. В режиме супервизора мбгут быть использованы все команды и средства машины. Многие части операционной системы работают в этом
6.2. Машинно-зависимые свойства операционных систем 319 режиме. При этом в режиме пользователя недопустимо выпол- нение некоторых команд. К таким командам могут относиться, например, функции ввода-вывода, установка флагов защиты или переключение ЦП из одного режима в другой. Примеры таких команд будут рассмотрены ниже. Ограничения, накладываемые на использование привилегированных команд, заставляют про- грамму пользователя обращаться к услугам операционного окружения. Таким образом, вместо непосредственного использо- вания функций базового аппаратного обеспечения программы должны иметь дело с интерфейсом расширенной машины. Огра- ничения также не дают программам пользователей вмешиваться случайно или намеренно в функции управления ресурсами, осу- ществляемые операционной системой. Привилегированные команды, равно как и режим пользователя/супервизора (или эквивалентный ему), необходимы практически для всех систем, поддерживающих одновременную работу более чем одного поль- зователя. В разд. 6.2 и 6.3 рассматриваются разнообразные функции и услуги, обычно предоставляемые операционным окружением. На этом уровне между операционными системами, которые мо- гут оказаться совершенно различными в интерфейсе пользова- теля, есть много общего. Многие рассматриваемые технические приемы могут быть использованы с некоторыми изменениями почти во всех операционных системах: пакетной обработки, раз- деления времени, реального времени и т. п. 6.2. Машинно-зависимые свойства операционных систем Одной из наиболее важных функций операционной системы является управление ресурсами ЭВМ, на которой она работает. Многие ресурсы имеют непосредственное отношение к аппарат- ным устройствам, таким как центральная оперативная память, каналы ввода-вывода и ЦП. Таким образом, многие функции операционной системы тесно связаны с архитектурой машины. Рассмотрим, например, машину УУМ. У нее маленькая опе- ративная память, отсутствуют каналы ввода-вывода, прерыва- ния, нет команд вызова супервизора. Такая машина может быть удобна в качестве персональной ЭВМ; на ней нет смысла работать одновременно нескольким пользователям. Таким об- разом, операционная система для стандартной машины УУМ будет однопользовательской с простыми средствами общения с пользователем и минимальным набором функций операционного окружения. И если она предоставит какие-то простые возмож- ности, то их вряд ли будет больше тех, что рассматривались в разд. 6.1.
320 Гл. 6. Операционные системы ЭВМ УУМ/ДС, наоборот, имеет гораздо большую оператив- ную память, каналы ввода-вывода и обладает многими другими свойствами, отсутствующими у стандартной машины УУМ. На УУМ/ДС хорошо иметь мультипрограммную операционную си- стему. Она позволит распределять между несколькими одно- временно работающими пользователями доступные им ресурсы расширенной машины, а также лучше использовать усовершен- ствованные программные средства. Конечно, разделение вычис- лительной системы между несколькими пользователями создает много проблем, подобных распределению ресурсов. Все они должны быть решены операционной системой. В дополнение к этому операционная система должна осуществлять поддержку более развитых функций аппаратуры, таких как прерывания и канальный ввод-вывод. В данном разделе мы рассмотрим некоторые функции ма- шинно-зависимых частей операционных систем. Для этого бу- дет использована терминология ЭВМ УУМ/ДС; однако неко- торые принципы могут быть легко перенесены на другие маши- ны, у которых архитектурные особенности схожи с УУМ/ДС. Мы обсудим в ходе изложения ряд важных свойств аппарат- ных средств УУМ/ДС. Для упрощения ссылок все эти свой- ства резюмированы в приложении В. Раздел 6.2.1 знакомит читателя с основными принципами прерываний и их обработки, используемыми на всем протяже- нии остальной части главы. В разд. 6.2.2 обсуждаются вопросы, связанные с распределением ЦП между несколькими заданиями пользователей, работающими в мультипрограммном режиме. В разд. 6.2.4 и 6.2.5 обсуждаются вопросы разделения цент- ральной памяти между несколькими пользовательскими зада- ниями. В разд. 6.2.4 дается представление о средствах управ- ления реальной памятью, а в разд. 6.2.5 — о важном понятии — виртуальной памяти. 6.2.1. Обработка прерываний Прерывание (interrupt) — это сигнал, заставляющий ЭВМ менять обычный порядок исполнения потока команд. Возник- новение подобных сигналов обусловлено такими событиями, как завершение операций ввода-вывода, истечение заранее задан- ного интервала времени или попытка деления на нуль. Рис. 6.2 дает представление о последовательности событий, происходящих в ответ на прерывание. Предположим, что в мо- мент поступления от некоторого источника сигнала прерывания программа А находится в решении. В результате управление автоматически передается на блок обработки прерываний (или блок ОП, а также обработчик прерываний), который обычно является частью операционной системы. Этот блок преднаэна-
6.2. Машинно-зависимые свойства операционных систем 321 чен для выполнения некоторых действий в ответ на условие, вызвавшее прерывание. После завершения обработки управле- ние может быть снова передано в ту точку программы А, где ее выполнение было прервано. В только что описанной последовательности событий воз- никновение и обработка прерывания могут быть совершенно не Программа А Рис. 6.2. Основная концепция обработки прерываний. связаны с программой А. Например, оно может быть вызвано завершением операции ввода-вывода, выданной . другой про- граммой. В общем случае невозможно предсказать, когда и по какой причине программа А будет прерва- Тип на. Другими словами, по отношению к ней «ласе прерывания прерывания возникают асинхронно. За со- i svc хранением текущего состояния машины во п время прерывания программы А, а также ш за его восстановлением, когда А будет про- ,v Программное По таймеру Ввода/вывода должена, следят аппаратные и програм- рис 53 типы прг< мные средства. Благодаря этому в случае рываний в УУМ/ДС, прерывания ничто, за исключением вре- мени, не влияет на ее выполнение. На самом деле для А даже не существует способа узнать, имело место прерывание или нет. На рис. 6.3 приведены четыре класса прерываний для ЭВ/Ц УУМ/ДС. SNC-прерывание (класс I) возникает при выполне- нии ЦП команды вызова супервизора. Эта команда использу- ется, программами для вызова функций операционной системы. Программное прерывание (класс II) возникает при появлении некоторой ситуации, такой как деление на нуль, или при попыт- ке выполнить неправильную машинную команду и, возможно, 11 Зак. 792
322 Гл. 6. Операционные системы а Рис. 6.4. Операции контекстного переключения, вызванные а — прерывание^ по таймеру и б —командой LPS 166.
6.2. Машинно зависимые свойства операционных систем 323 Центральная память Новый SW Новый PC Старый SW Старый PC Рабочая область SVC-пре- рываний Рабочая область Программ- ных пре- рываний Рабочая область прерывания' по таймеру Рабочая область Прерывания” по ВВОДУ’ выводу Рис. 6.4. Продолжение. И* 100 103 106 109 ЮС Область сохранения регистров Регистры Новый SW Новый-РС 130 133 136 139 13С Старый SW Старый PC Область сохранения регистров Новый SW Новый PC Старый SW Старый PC 160 163 166 169 16С Область сохранения регистров Новый S W Новый PC Старый SW Старый PC 190 193 196 199 190 Область сохранения регистров EZ® I S
324 Гл. 6. Операционные системы в процессе работы программы. Приложение В содержит пол- ный перечень условий, которые могут вызвать программное прерывание. Прерывание по таймеру (класс III) вызывается интерваль- ным таймером ЦП. Этот таймер содержит регистр, которому может быть присвоено определенное начальное значение по- средством привилегированной команды STI. Значение этого регистра автоматически уменьшается на 1 после использова- ния каждой миллисекунды времени ЦП. Когда это значение становится равным нулю, происходит прерывание по таймеру. Подобный интервальный таймер используется операционной си- стемой для определения времени, в течение которого программа пользователя может оставаться под управлением машины. Прерывание по вводу-выводу (класс IV) вызывается кана- лами или устройствами ввода-вывода. Причиной многих таких прерываний является нормальное завершение некоторой опера- ции ввода-вывода; однако они могут также оповещать о воз- никновении различных ошибочных ситуаций. Когда происходит прерывание, состояние ЦП сохраняется, а управление передается стандартной программе обработки пре- рываний. В заключении рассмотрим метод для УУМ/ДС. Как показано на рис. 6.4, в машине УУМ/ДС для каждого класса прерываний имеется соответствующая ему рабочая об- ласть прерываний. Например, область, соответствующая преры- ванию по таймеру, начинается с адреса памяти 160. Когда про- исходит прерывание по таймеру, содержимое всех регистров сохраняется в этой области (см. рис. 6.4а). Затем из двух пер- вых слов области заранее занесенные туда значения загружа- ются в слово состояния SW и счетчик команд PC. Загрузка и сохранение регистров осуществляются аппаратными средствами машины автоматически. Загрузка счетчика команд новым значением адреса автома- тически вызывает передачу управления на соответствующую команду. Этот адрес, заранее сохраненный в рабочей области прерывания, представляет собой начальный адрес стандартной программы обработки прерываний по таймеру. Загрузка слова SW также вызывает определенные изменения в состоянии ЦП. После выполнения в ответ на запрос на прерывание любого требуемого действия стандартная программа обработки преры- ваний выполняет команду загрузки состояния процессора (LPS — Load Processor Status), в результате чего управление передается прерванной программе (см. рис. 6.46). Команда LPS вызывает загрузку сохраненного содержимого SW, PC и других регистров из соответствующих слов области сохоане- ния, начиная с адреса, указанного в команде. Это приводит к восстановлению содержимого регистров и состояния ЦП, кото- рые были в момент прерывания. Управление затем передается
6.2. Машинно-зависимые свойства операционных систем 32 5 на команду, перед выполнением которой произошло прерывание. Ролр$Ц£УЦ£>,и восстановление состояния ЦП и содержимого ре- гистров часто называют операцией контекстного переключения. Слово состояния SW содержит часть информации, которая нужна для обработки прерываний. Мы говорим о содержимом SW в УУМ/ДС. Большинство ЭВМ имеет аналогичный регистр, часто называемый словом состояния программы или словом состояния процессора. Разряды Имя поля Использование 0 MODE 0=режим пользователя, 1= режим супервизора 1 IDLE 0=активен, 1=пассивен 2-5 ID Идентификатор процесса 6-7 СС Код условия 8-11 MASK Маска прерываний 12-15 Не используется 16-23 ICODE Код прерываний Рис. 6.5. Содержимое слова состояния УУМ/ДС. На рис. 6.5 показано содержимое SW. Первым битом (MODE) задается режим, в котором находится ЦП — пользовательский или супервизора. Обычные программы выполняются в пользовательском ре- жиме (MODE = 0). Когда происходит прерывание, новое за- гружаемое содержимое SW имеет MODE = 1, что автоматиче- ски переводит ЦП в режим супервизора, тем самым становится возможным использование привилегированных команд. Перед тем как значение SW будет сохранено, в поле ICODE авто- матически устанавливается значение, указывающее на причину прерывания. В случае SVC-прерываний ICODE присваивается значение, заданное пользователем командой SVC. Он опреде- ляет тип сервисного запроса. При программном прерывании в ICODE отражен тип вызвавшего его условия, например деление на нуль. При прерывании по вводу-выводу в ICODE дан номер канала, породивший прерывание. Дальнейшую информацию о возможных значениях ICODE можно найти в приложении В. В SW содержится также код состояния СС. Сохранение SW автоматически спасает значение кода состояния прерванно- го процесса. Об использовании полей IDLE и ID будет расска- зано ниже в этой главе. Поле IDLE определяет, выполняет ли ЦП команды или простаивает. В ID содержится 4-битовое зна- чение, идентифицирующее текущую выполняемую программу. Оставшееся поле слова состояния (MASK) используется для контроля за разрешением прерываний. Это требуется для того, чтобы избежать потери сохраненной ранее информации о состоянии процесса. Предположим, например, что произошло прерывание по вводу-выводу. Значения SW, PC и других ре-
326 Гл. 6. Операционные системы гиотров будут сохранены в рабочей области прерывания ввода- вывода, о которой только что было рассказано, а ЦП начнет выполнение обработчика прерываний по вводу-выводу. Если до конца обработки первого прерывания произойдет еще одно, снова будет иметь место контекстное переключение. Однако на этот раз в качестве содержимого регистров, сохраняемого в ра- бочей области, окажутся значения, используемые обработчиком прерываний. Значения же, сохраненные при первом прерывании, будут утрачены, поэтому вернуть управление программе поль- зователя, которая в тот момент выполнялась, будет нельзя. Чтобы избежать этого, нужно не допустить наступления пре- рываний определенного типа, пока первое из них не будет об- работано. Это достигается использованием поля MASK слова состояния. В MASK каждый бит соответствует некоторому классу прерываний. Если какой-то бит установлен в 1, то пре- рывания соответствующего класса разрешены, если в 0, то за- прещены. В последнем случае говорят, что они маскированы (часто их также называют запрещенными или закрытыми). Однако маскированные прерывания не теряются, потому что сигнал, вызвавший прерывание, сохраняется аппаратурой. Вре- менно задержанное таким способом прерывание называется от- ложенным. Когда, вследствие того что MASK сброшена, преры- вания соответствующего класса вновь разрешаются, сигнал опо- знается и происходит прерывание. В УУМ/ДС маскирование прерываний находится под конт- ролем операционной системы и зависит от значения MASK в SW, которое заранее сохраняется в рабочей области каждого прерывания. Можно запретить все прерывания, установив все биты MASK в 0. Однако в действительности поступать подоб- ным образом нет необходимости. Каждому классу прерываний на УУМ/ДС присвоен опреде- ленный приоритет прерываний. Наивысший приоритет имеют SVC-прерывания (Класс I), затем идут программные (Класс II) и т. д.1). Поле MASK в слове состояния устанавливается в со- ответствии с классом прерывания так, чтобы все прерывания с равным или более низким приоритетом были запрещены, а с более высоким разрешены. Например, слово состояния, загру- женное в результате программного прерывания, будет иметь биты MASK, соответствующие прерываниям — программным, по таймеру и по вводу-выводу, установленными в 0; эти классы прерываний будут запрещены. Бит в MASK для SVC-прерыва- ний будет установлен в 1, поэтому они будут разрешены. Ко- гда прерывания по завершении работы обработчика прерываний открываются, среди ожидающих может оказаться более одного Не во всех операционных системах приоритеты распределены так, как предлагает автор. — Прим, ред,
6.2. Машинно-зависимые свойства операционных систем 327 класса прерываний (например, по таймеру и по вводу-выводу). В этом случае первым опознается то, что имеет более высокий приоритет. Если используется такая система приоритетов, то програм- ма обработки прерываний сама может быть прервана. Каким образом происходит вложенное прерывание, показано на рис. 6.6. По прерыванию по вводу-выводу состояние програм- мы А, выполняющейся в этот момент на ЦП, сохраняется, а управление передается обработчику прерываний по вводу-выво- ду. Во время его работы происходит новое прерывание — уже Программа А Рис. 6.6. Пример вложенного прерывания. по таймеру, в результате чего управление передается обработ- чику прерываний по таймеру. По завершении обработки этого прерывания при помощи команды LPS из рабочей области прерывания по таймеру восстанавливается состояние ЦП. В ре- зультате управление снова передается обработчику прерываний по вводу-выводу. Так как опять загружено старое значение MASK, прерывания по таймеру, которые были запрещены, сно- ва открываются. Однако прерывания по вводу-выводу по-преж- нему остаются закрытыми. После завершения обработки пре- рывания при помощи уже другой LPS восстанавливается состояние ЦП, которое было в момент первого прерывания. Те- перь все прерывания открыты, потому что в слове состояния, используемом программой А, все биты MASK установлены в 1. Ниже мы увидим, как прерывания могут быть использованы при реализации таких функций операционной системы, как планирование процессов, управление вводом-выводом и распре- деление памяти. '
328 Гл. 6. Операционные системы 6.2.2. Планирование процессов Процессом (process)у или заданием (task)у часто называет- ся программа, находящаяся в решении. Другие возможные опре- деления процесса можно найти в Дейтел [1984]. Для выполне- ния вычислительной работы операционная система выделяет процессам ЦП. В однопрограммной операционной системе при- сутствует только один пользовательский процесс. Однако в мультипрограммной системе на ресурсы может претендовать много независимых процессов. Планирование процессов — это управление распределением ресурсов ЦП между различными конкурирующими процессами путем передачи им управления согласно некоторой стратегии планирования. Во многих случаях процесс соответствует заданию пользова- теля. Однако некоторые операционные системы позволяют од- ному заданию создавать несколько различных процессов, вы- полняемых одновременно. Вдобавок некоторые системы разре- шают одной программе быть разделенной между несколькими независимыми процессами. Дальнейшую информацию по этому вопросу можно найти в Дейтел [1984]. Здесь мы будем счи- тать, что каждому процессу соответствуют только одна про- грамма и одно задание пользователя. Процесс создается, когда выполнение задания пользователя начинается, и уничтожается, когда задание завершается. Во время своего существования процесс может находиться в трех состояниях. Процесс активен (running) у когда он использует цп для выполнения своих команд. Процесс блокирован (blocked) у если его выполнение может быть продолжено только после наступления некоторого ожидаемого им события. Напри- мер, процесс может быть блокирован, потому что ему требует- ся ждать завершения операции ввода-вывода. Процессы, кото- рые не блокированы и не активны, называются находящимися в состоянии готовности (ready). Этим процессам будет переда- но управление после того, как текущий активный процесс его отдаст. На рис. 6.7 показаны возможные переходы из одного со- стояния в другое. В любой момент времени активным (т. е. использующим ЦП) может быть только один процесс. При пе- редаче управления процессу пользователя операционная систе- ма устанавливает интервальный таймер. Тем самым задается квант времен^ являющийся максимальным количеством време- ни ЦП, на которое процесс получает управление. Если это вре- мя истекает, процесс переводится из состояния активности в со- стояние готовности. После этого операционная система, согласно стратегии планирования, выбирает следующий процесс, нахо- дящийся в готовности, переводит его в состояние активности и передает ему управление. Выбор процесса и передачу на него
6.2. Машинно-зависимые свойства операционных систем 329 управления часто называют диспетчеризацией. Часть операци- онной системы, выполняющая эту функцию, называется диспет- чером (dispatcher). Может оказаться, что активный процесс, не использовав пол- ностью предоставленного ему кванта времени, будет ожидать наступления некоторого события, например завершения опера- ции ввода-вывода. В этом случае активный процесс блокирует- ся, а какой-то новый процесс активизируется. Когда же ожидае- мое событие наступает, соответствующий заблокированный про- цесс переводится в состояние готовности и может снова стать Рис. 6.7. Изменение состояний процесса. кандидатом на обслуживание. Операции ожидания события и оповещения о том, что событие наступило, реализуются при по- мощи сервисных запросов операционной системе (с использова- нием SVC) 1). Механизм, чаще всего применяемый для уста- новления соответствия между процессами и ожидаемыми ими событиями, рассматривается ниже в данном разделе. Обычно до своего завершения процесс много раз пребывает в состоянии активности, готовности и блокировки. Для того чтобы это никак не повлияло на результаты вычислений, каж- дый раз, когда процесс теряет активность, его текущее состоя- ние должно быть сохранено. Когда же процесс снова будет ак- тивизирован, это состояние должно быть восстановлено. Ин- формация о состоянии каждого процесса хранится операцион- ной системой в блоке состояния процесса (PSB — Process Sta- tus Block). Блок состояния процесса создается, когда процесс входит в решение, и уничтожается, когда процесс завершается. Блок PSB содержит информацию о том, в каком состоянии процесс находился (активности, готовности или блокировки). В нем имеется область, используемая для сохранения машин- ных регистров (включая SW и PC) и другой всевозможной информации (например, о системных ресурсах, используемых процессом). ° Не обязательно с помощью SVC> но и по прерываниям! -~Прим. ред<
330 Гл. 6. Операционные системы Общий вид алгоритма, используемого при диспетчеризации, показан на рис. 6.8. При передаче управления от одного про- цесса другому прежде всего необходимо сохранить информацию о состоянии активного процесса. Если процесс был заблокиро- ван, так как он использовал весь свой квант времени, то информация о состоянии может быть найдена в рабочей обла- сти прерывания по таймеру. Если процесс отдал управление (выполнив SVC-запрос), потому что ему требуется дождаться наступления некоторого события, информация о состоянии бу- дет храниться в рабочей области SVC-прерываний. Конечно, procedure DISPATCH обновить FSB Активного ттроцесса (если он ееть> выбрать следующий Готовый процесс для передачи ему управления if готовый процесс найден then begin пометить выбранный процесс как Активный выделить квант времени,, установив командой STI интервальный таймер командой LPS передать управление выбранному процессу end else командой LPS перевести ЦП в состояние простоя' Рис. 6.8. Алгоритм диспетчеризации. может оказаться, что активных процессов нет. Это может слу- читься, например, если все процессы в системе находятся в заблокированном состоянии. Тогда информацию о состоянии сохранять не надо. После сохранения состояния предыдущего активного процес- са диспетчер выбирает для активизации новый процесс. Чтобы задать квант времени, выделяемый выбранному процессу, дис- петчер устанавливает интервальный таймер, затем, используя команду LPS для загрузки информации о состоянии, хранимой в PSB данного процесса, осуществляет передачу управления. Если процесса, находящегося в состоянии готовности, нет, то диспетчер для перевода ЦП в состояние простоя (idle), исполь- зует LPS, загружая слово состояния с IDLE = 1 (рис. 6.5). Выбор следующего процесса для диспетчеризации осущест- вляется несколькими способами. В первом из них, называемом круговым (robin round), все процессы считаются равноцен- ными. Диспетчер циклически просматривает все PSB, выбирая следующий процесс из тех, что находятся в состоянии готов- ности. Каждому активизируемому процессу предоставляется одинаковый квант времени. В более сложных методах диспетчеризации выбор процессов происходит по приоритетам. В некоторых системах приоритеты определены заранее в соответствии с характером заданий поль- зователей. Задачей таких систем является обеспечение для
6.2. Машинно-зависимые свойства операционных систем каждого класса заданий необходимого уровня сервиса. В дру< гих системах приоритеты ^могут назначаться самой операцион- ной системой в целях увеличения производительности всей си- стемы. Приоритеты могут меняться и динамически в зависимо- сти от загрузки и производительности системы. Возможно, что наряду с использованием системы приоритетов различным про- цессам будут выделены различные кванты времени. Дальней- шее обсуждение этих и более сложных способов диспетчериза- ции может быть найдено в Дейтел [1984] и Лорин [1981]. procedure WAIT(ESB> if ESBFLAG = 1 then (событие уже наступило} возвратить управление запрашивающему процессу командой LPS else begin пометить запрашивающий процесс как Влокирое^ННЫЙ занести запрашивающий процесс В ESBQUEUE DISPATCH end ’а • procedure SIGNAL(ESB) ESBFLAG s = 1 (отметить, что событие наступило} Гог каждый процесс в ESBQUEUE do begin пометить процесс как Готовый убрать процесс из ESBQUEUE end возвратить управление запрашивающему процессу командой LPS б Рис. 6.9. Алгоритмы обработки WAIT (SVC 0) и SIGNAL (SVC 1). Когда активный процесс достигает точки, в которой ему требуется ждать наступления некоторого события, он сообщает об этом операционной системе при помощи запроса на обслу- живание WAIT (SVC 0). О наступлении события, которого мо- гут ожидать и другие процессы, операционной системе сообща- ется посредством запроса SIGNAL (SVC 1). В разд 6.2.3 бу- дут даны примеры использования WAIT и SIGNAL; здесь же мы коснемся того, как эти запросы соотносятся с функцией планирования процессов. На рис. 6.9 приводится последовательность действий, выпол- няемых операционной системой в ответ на подобные сервисные запросы. Событие, которое ожидалось или о наступлении кото- рого было сообщено, определяется посредством задания адреса соответствующего блока состояния события (ESB — Event Sta- tus Block). Блок состояния события содержит битовый флаг
332 Гл. 6. Операционные системы ESBFLAG, в котором отмечается, произошло ли соответствую- щее событие или нет. Блок ESB содержит также указатель на ESBQUEUE — список процессов, ожидающих в этот момент наступления данного события. Дальнейшая информация о ESB, примерах их создания и использования приводится в разд. 6.2.3. Запрос WAIT выдается активным процессом и говорит о том, что процесс не может продолжить работу до наступления События, соответствующего ESB. Таким образом, в алгоритме обработки WAIT первым делом исследуется ESBFLAG и, если событие уже наступило, управление возвращается запрашиваю- щему процессу. Если событие еще не наступило, активный про- цесс переводится в состояние блокировки и заносится в ESBQUEUE. Запрос SIGNAL выдается процессом, обнаружившим насту- пление некоторого события, соответствующего ESB. Следова- тельно, установкой ESBFLAG алгоритм для обработки SIGNAL отмечает, что событие наступило. Затем осуществляется скани- рование ESBQUEUE, списка ожидающих этого события процес- сов. Каждый процесс в списке переводится из состояния блоки- ровки в состояние готовности. Затем управление возвращается процессу, сделавшему запрос SIGNAL. Если используемый метод диспетчеризации основан на при- оритетах, для обработки SIGNAL применяется несколько другой алгоритм. В таких системах может случиться, что один или бо- лее процессов, перешедших в состояние готовности, имеют более высокий приоритет, чем текущий активный процесс. Учитывая это, алгоритм для обработки SIGNAL вместо прямой передачи управления активному процессу должен сначала вызвать дис- петчер, который и передаст управление процессу с наивысшим приоритетом, находящемуся в текущий момент в состоянии го- товности. Этот способ называется планированием процессов по приоритетам. Он позволяет процессу, перешедшему в состояние готовности, перехватить управление у процесса, активного в этот момент, но имеющего меньший приоритет, не ожидая ис- течения кванта времени этого процесса. 6.2.3. Обслуживание ввода-вывода На типичной мини-ЭВМ, такой как УУМ, ввод-вывод осу- ществляется побайтно. Например, для чтения данных програм- ма должна иметь цикл, в котором опрашивается состояние уст- ройства ввода-вывода и выполняется ряд команд чтения дан- ных. В подобных системах ЦП участвует в передаче и приеме каждого байта с устройства ввода-вывода. Пример программи- рования ввода-вывода такого рода показан на рис. 2.1. В более совершенных ЭВМ, таких как УУМ/ДС, для отсле- живания всех деталей передачи данных и управления вво-
6.2. Машинно-зависимые свойства операционных систем 333 дом-выводом используются каналы ввода-вывода. На рис. 6.10 приведена типичная для УУМ/ДС конфигурация ввода-вывода. Пусть, например существует 16 каналов, к каждому из которых может быть подключено до 16 устройств. В номер, присвоенный устройству ввода-вывода, входит и номер канала, к которому оно подключено. Например, устройство, обозначенное 20—2F, подключено к Каналу 2. Устройству Рис. 6.10. Типичная конфигурация ввода-вывода для УУМ/ДС. Последовательность операций, которые должен выполнить канал, задается канальной программой, состоящей из набора канальных команд. Для осуществления операции ввода-вывода ЦП выполняет команду ввода-вывода START I/O (SIO), в ко-» торой задается номер канала и начальный адрес канальной программы. Затем канал выполняет указанную операцию вво« да-вывода без дальнейшего вмешательства ЦП. По завершении своей программы канал генерирует прерывание по вводу-выво- ду. Одновременно могут работать несколько каналов, каждый из которых выполняет свою собственную канальную програм- му; таким образом, в одно и то же время могут осуществляться несколько различных операций ввода-вывода. Каждый канал работает независимо от ЦП, поэтому, пока выполняются опера- ции ввода, ЦП может продолжать вычисления. Операционная система ЭВМ типа УУМ/ДС вовлекается в процесс ввода-вывода в нескольких случаях. Система должна принять запросы на ввод-вывод от пользовательских программ и сообщить им, когда операции будут выполнены. Она может
ЗЗГ4 Гл. 6. Операционные системы также управлять работой каналов ввода-вывода и обрабатывать генерируемые ими прерывания. В оставшейся части раздела мы обсудим, как эти функции выполняются, и проиллюстрируем этот процесс несколькими примерами. Программа на УУМ/ДС запрашивает операцию ввода-вы- вода посредством выполнения команды SVC 2, параметрами которой являются номер канала, начальный адрес канальной программы и адрес блока состояния события, используемый при оповещении о завершении операции ввода-вывода. Если программа должна ждать результаты операции ввода-вывода, то она выполняет команду SVC О (WAIT). Этой командой за- дается адрес блока состояния события, соответствующего ожи- даемой операции ввода-вывода. Таким образом, выполнение операции ввода-вывода имеет вид SVC 2 {запрос операции ввода-вывода} SVC 0 {ожидание результата} В некоторых случаях WAIT может следовать сразу за запро- сом операции ввода-вывода. Однако, так как вычисления и ввод-вывод могут идти одновременно, имеется возможность про- должить выполнение программы, пока ожидаются результаты операции ввода-вывода. Более подробно это иллюстрируется программой на рис. 6.11. Сначала программа загружает в регистры начальный адрес ка- нальной программы, номер канала и адрес блока состояния события. После этого для запроса операции ввода-вывода про- грамма выполняет команду SVC. Канальная программа для чтения, задаваемая в виде последовательности элементов дан- ных, содержит две канальные команды. Первой командой зада- ется операция чтения, которая должна быть выполнена на под- соединенном к каналу устройстве с номером 1; 256 байт дан- ных должны быть размещены в памяти, начиная с адреса BUFIN. Вторая команда вызывает останов канала и генерирует прерывание по вводу-выводу. Блок состояния события состоит из 3-байтовой области данных. Первым битом ESB является флаг, используемый для индикации, наступило уже соответ- ствующее событие или нет (0 = нет, 1 =да). Остальная часть ESB служит для сохранения указателя на очередь процессов, ожидающих это событие. Если ждущих процессов нет, то зна- чение указателя равно нулю. Так, начальное значение ESB Х'ОООООО' показывает, что соответствующее событие еще не наступило, и что ожидающие его процессы отсутствуют. Даль- нейшие подробности, касающиеся формата канальных команд.
6.2. Машинно-зависимые свойства операционных систем =335 Pl START О • (инициализация) LDA IREAD АДРЕС КАНАЛЬНОЙ ПРОГРАММЫ IDS ♦t НОМЕР КАНАЛА LDT *ESB АДРЕС БЛОКА СОСТОЯНИЯ СОБЫТИЯ SVC 2 ВЫДАЧА ЗАПРОСА НА ЧТЕНИЕ LOOP LDA *ESB АДРЕС ESB SVC 0 ОЖИДАНИЕ КОНЦА ЧТЕНИЯ {запись данных в рабочую область программы! LDA *0 ИНИЦИАЛИЗАЦИЯ ESB STA ESK LDA *READ LDS *1 LDT tESB SVC 2 ' ВЫДАЧА СЛЕДУЮЩЕГО ЗАПРОСА НА ЧТЕНИЕ Сданные процесса! J LOOP . КАНАЛЬНАЯ ПРОГРАММА ДЛЯ ЧТЕНИЯ . ПЕРВАЯ КОМАНДА — READ BYTE Х'11л КОМАНДА = READ, УСТРОЙСТВО * 1 BYTE Х'0100' СЧЕТЧИК ВАЙТОВ = 256 HORD 3BUFIM АДРЕС БУФЕРА ВВОДА . ВТОРАЯ КОМАНДА ... BYTE Х'ШШШ' ОСТАНОВ КАНАЛА ESB BYTE Х'000000' БЛОК СОСТОЯНИЯ СОБЫТИЯ ДЛЯ ЧТЕНИЯ BUFIN RESB 256 БУФЕРНАЯ ОБЛАСТЬ ДЛЯ ЧТЕНИЯ END Рис. 6.11. Пример выполнения ввода-вывода с использованием запросов. УУМ/ДС и блока состояния события, можно найти в прило- жении В. После выдачи запроса на ввод-вывод программа на рис. 6.11 выполняет команду SVC 0. Регистр А содержит ад- рес ESB, соответствующего ожидаемому событию. Таким собы- тием в данном случае является только что затребованная опе- рация ввода-вывода. После завершения операции чтения про- грамма заносит введенные данные в рабочую область. Затем, программа опять инициализирует ESB и запрашивает опера- цию ввода-вывода для чтения следующих 256 байт данных.
' 836 - Гл. 6.- Операционные системы j -------——“---------------------------------------------- Пока эта операция выполняется, программа может обрабаты- вать уже прочитанные данные, совмещая тем самым функции вычисления и ввода. По завершении обработки управление сно- ва передается на начало основного цикла для ожидания конца следующей операции чтения. Несколько более сложный пример приведен на рис. 6.12. В этом случае программа копирует 4096 байт записей данных с устройства 22 на устройство 14. Имеются две канальные про- граммы: одна для чтения и другая для записи, а также два блока состояния события. В основном цикле программы сначала выдается запрос на чтение, а затем ожидается завершение это- го чтения и предыдущей записи. По окончании обеих операций программа создает последовательность вывода и выдает запрос на запись. Однако в этот момент ESB для операции записи имеет начальное значение X'SOOOOO', где первый бит равен 1. Этим отмечается, что соответствующее событие уже наступило и при вызове стандартной программы WAIT операционной си- стемы управление будет сразу возвращено программе пользова- теля (рис. 6.9). Операции ввода-вывода в программе на рис. 6.12 выполня- ются независимо друг от друга, так как ими используются разные каналы. Одна операция может быть завершена раньше другой. Возможно также, что обе операции будут выполнены фактически одновременно. Программа в состоянии координиро- вать взаимосвязанные операции ввода-вывода, так как им со- ответствуют разные ESB. На примере этой программы показа- но, как каналы могут использоваться для выполнения совме- щенных операций ввода-вывода. Ниже такое совмещение будет рассмотрено подробнее. В программах на рис. 6.11 и 6.12 показано, как осуществля- ются запросы на ввод-вывод с точки зрения пользователя. Сейчас мы готовы обсудить, как они в действительности обра- батываются машиной и операционной системой. В аппаратуре УУМ/ДС предусмотрено наличие в памяти, соответствующей каналу ввода-вывода, рабочей области канала. В ней содер- жатся стартовый адрес текущей канальной программы, если она имеется, и адрес ESB, соответствующего текущей операции. Результат работы операции ввода-вывода после ее завершения отображается находящимися в рабочей области канала флага- ми состояния, такими как нормальное завершение, ошибка вво- да-вывода или устройство недоступно. Рабочая область про- граммы содержит также указатель на очередь запросов на ввод-вывод для данного канала, которая поддерживается стан- дартными программами операционной системы. Дополнитель- ные сведения о том, где располагаются рабочие области кана- лов УУМ/ДС и что в них находится, содержатся в прило- жении В.
6.2. Машинно-зависимые свойства операционных систем 337 Р2 START 0 • W {инициализация} LOOP LDA *0 ИНИЦИАЛИЗИРОВАТЬ ESB ДЛЯ ЧТЕНИЯ STA RDESB LDA ♦READ ВИДАТЬ ЗАПРОС НА ЧТЕНИЕ С УСТРОЙСТВА 22 LDS ♦2 LDT ♦RDESB SVC 2 LDA ♦RDESB ЖДАТЬ КОНЦА ЧТЕНИЯ SVC 0 LDA ♦WRESB ЖДАТЬ ЗАВЕРШЕНИЯ ПРЕДЫДУЩЕЙ ЗАПИСИ SVC 0 • tt {формирование последовательности вывода} LDA ♦0 ИНИЦИАЛИЗИРОВАТЬ ESB ДЛЯ ЧТЕНИЯ STA WRESB LDA ♦WRITE ВЫДАТЬ ЗАПРОС НА ЧТЕНИЕ С УСТРОЙСТВА 14 LDS ♦1 LDT ♦WRESB SVC 2 J LOOP * КАНАЛЬНАЯ ПРОГРАММА ДЛЯ ЧТЕНИЯ ПЕРВАЯ КОМАНДА — READ BYTE Х'12' КОМАНДА « READ, УСТРОЙСТВО « 2 BYTE хчвее' СЧЕТЧИК БАЙТОВ = 4096 WORD BUFIN АДРЕС БУФЕРА ВВОДА * ВТОРАЯ КОМАНДА — BYTE x'mmwwwm' останов канала • КАНАЛЬНАЯ ПРОГРАММА ДЛЯ ЗАПИСИ ПЕРВАЯ КОМАНДА — WRITE BYTE Х'24' КОМАНДА = WRITE, УСТРОЙСТВО « 4 BYTE Х'1000' СЧЕТЧИК БАЙТОВ « 4096 WORD BUFOUT АДРЕС БУФЕРА ВЫВОДА ВТОРАЯ КОМАНДА — BYTE Х'000000000000' ОСТАНОВ КАНАЛА RDESB BYTE X'000000' БЛОК СОСТОЯНИЯ СОБЫТИЯ ДЛЯ ЧТЕНИЯ WRESB BYTE Х'800000' БЛОК СОСТОЯНИЯ СОБЫТИЯ ДЛЯ ЗАПИСИ BUFIN RESB 4096 БУФЕР ВВОДА BUFOUT RESB 4096 БУФЕР ВЫВОДА END Рис. 6.12. Программа, иллюстрирующая многократный запрос на ввод-вывод* На рис. 6.13 отмечены действия, предпринимаемые операци- онной системой в ответ на запрос ввода-вывода от программы пользователя. Если требуемый канал занят выполнением другой
333 Гл. 6. Операционные системы операции, операционная система ставит запрос в очередь к- нему. В противном случае она запускает канал, а текущий за- прос сохраняет в рабочей области канала. Возможно также, что управление будет возвращено процессу, затребовавшему ввод-вывод, и он, пока ведется обмен, сможет продолжить рач боту. procedure IOKEQ(CHAN,CP,ESB) опросить канал командой НО if канал занят then поставить (CF*rESB> в очередь к каналу else begin сохранить (CF\ESD) в рабочей области канала запустить канал командой SIC? end возвратить управление запрашивающему процессу командой LPS Рис. 6.13. Алюритм обработки запроса на ввод-вывод (SVC 2). На рис. 6.14 показаны действия, предпринимаемые операци- онной системой в ответ на прерывание по вводу-выводу. Номер канала, вызвавшего прерывание, находится в слове состояния, procedure lOINTERRUPT(CHAN) исследовать флаги состояния в рабочей области канала if нормальное завершение операции then begin взять адрес ESB из рабочей области канала командой SUC оповестить через ESB о наступлении события if очередь запросов к каналу не пуста then begin взять из очереди (CP,ESB) для следующего запроса сохранить (CPrESB) в рабочей области канала запустить канал командой SI0 end £if очередь не. пуста) end Cif нормальное завершение операции) else выбрать подходящие действия по исправлению ошибок if в момент наступления прерывания ЦП простаивал then DISPATCH else возвратить управление прерванному процессу командой LPS Рис. 6.14. Алгоритм обработки прерывания по вводу-выводу. хранящемся в рабочей области данного прерывания. Для опре- деления причины прерывания операционная система исследует флаги состояния в рабочей области канала. Если установлено, что операция ввода-вывода завершилась нормально, обработчик прерывания оповещает об этом операци- онную систему через заданный при запросе блок состояния со- бытия. Это может быть сделано как при помощи SVC-запроса, приводящего к вложенному прерыванию, так и непосредственно
6.2. Машинно зависимые свойства операционных систем 339 вызовом той части операционной системы, которая обрабатыва- ет запросы SIGNAL. В любом случае окончание операции вво- да-вывода отмечается в ESB. Каждый ожидающий этого про- цесс снова переводится в состояние готовности (см. разд. 6.2.2). Затем обработчик прерываний по вводу-выводу просматривает очередь запросов к данному каналу и начинает выполнение следующего запроса (если он есть). Если флагами отмечено какое-то ненормальное состояние, то операционная система осуществляет соответствующие дейст- вия по исправлению ошибок. Они, конечно, зависят от типа устройства ввода-вывода и характера обнаруженной ошибки. Например, обработка ошибки четности при работе с магнито- фоном обычно сводится к перемотке ленты назад и повторению операции ввода-вывода (некоторое число раз). С другой сторо- ны, когда кончилась бумага на печатающем устройстве, перед попыткой осуществить дальнейшие действия по исправлению ошибок оператору ЭВМ посылается сообщение. Если операци- онная система обнаружит, что ошибка ввода-вывода неустрани- ма, то она может окончить процесс, выдавший запрос на ввод- вывод, и послать соответствующее сообщение пользователю. Или, сохранив код ошибки в ESB, она может оповестить об этом запрашивающий процесс, который сам уже может принять решение, продолжить ему выполнение или нет. После окончания работы обработчик прерываний обычно возвращает управление прерванному процессу, восстанавливая его состояние. Однако если ЦП в этот момент простаивал, то должен быть вызван диспетчер. Делается это потому, что в результате обработки прерывания некоторый процесс мог пе- рейти в состояние готовности. Диспетчер будет вызван и в том случае, если использовалось приоритетное планирование про- цессов (см. разд. 6.2.2). Выполнение всех функций обслуживания ввода-вывода и планирования процессов демонстрируется на рис. 6.15. Два пользовательских процесса, обозначенные Р1 и Р2, выполняют- ся одновременно. Это те же процессы, что показаны на рис. 6.11 и 6.12. Мы считаем, что кванты времени, выделяемые каждому процессу, достаточно большие; поэтому прерывание по таймеру до того, как процессу требуется отдать управление по какой-то другой причине, обычно не происходит. На диа- грамме рис. 6.15 показана последовательность действий, выпол- няемых центральным процессором, разделенным между процес- сами пользователей, частями операционной системы и двумя каналами ввода-вывода. Начало шкалы времени на диаграмме находится вверху. Величина промежутков на шкале не обяза- тельно пропорциональна действительной длительности обозна- чаемого ими времени. Последовательные номера призваны по- мочь в использовании данного примера,
(1) £2) (3) (4) (5) (6) (7) (8) О) (10) ।________________________ЦП Блок обработки Pl Р2 Диспетчер SVC-лрерывадии . Каналы ------— । 1---------- Блок обработки прерываний по 1 2 вводу-выводу {а) 1 комментарии* Р1 запрашивает операцию ввода-вывода (а) р.1 ожидает завершения операции (а) P2 запрашивает операцию ввода-вывода (б) P2 ожидает завершения операций (б) ЦП начинает простаивать (11) (12) (13) С14> ’15) (16) (17) (18) (19) (20) (21) [ I 1 3 1 (22) (23) (24) (25) 126) (27) (28) (29) (30) (31) (32) Прерывание по вводу-выводу от канала Т Сообщение о завершении (а) -L Прерывание по вводу-выводу от канала 2 сообщение о завершении (б) pi запрашивает операцию ввода-вывода (в) Pi ожидает завершения операции (в) Р2 исследует esb, в котором отмечено завершение операции Р2 запрашивает операцию ввода-вывода (г) Т Р2 запрашивает операцию ввода-вывода (д) Р2 ожидает завершения операции (д) ЦП начинает простаивать Гл. 6. Операционные системы
(34) (35) (36) (37) (38) г (39) L (40) г (41) L (42) (43) (44) г (45) L (46) (47) (48) г (49) L (50) (51) (52) (53) Прерывание по вводу-выводу от Канала 1 Сообщение о завершении (в) Р1 запрашивает операцию ввода-ввода (е) J- Прерывание по вводу-выводу от Канала 2 Сообщение о завершении (д) (е) Прерывание по вводу-выводу от Канала 1 Сообщение о завершении (г) Р1 ожидает завершения операции (е) Р2 ожидает завершения операции (г) (уже завершенной) Рис. 6,15. Пример управления вводом-выводом и функции планирования процессов. 6.2. Машинно-зависимые свойства операционных систем
342 Гл. 6. Операционные системы В начале примера процессы Р1 и Р2 уже инициированы; Р1 был активизирован первым. Оба канала ввода-вывода сво- бодны. В момент (1) процесс Р1, выполнив команду SVC, осу- ществляет свой первый запрос ввода-вывода. Этим вызывается прерывание, в результате которого управление передается бло- ку обработки SVC-прерываний. Чтобы облегчить дальнейшие ссылки, на диаграмме эта операция ввода-вывода помечена (а). В запросе ввода-вывода задан свободный в этот момент канал с номером 1. Поэтому после запуска блока обработки SVC-пре- рываний канальной программы управление возвращается про- цессу Р1 (момент 2). В' (3) выполнением другой команды SVC (SVC 0) Р1 вы- дает для операции (а) запрос WAIT. Управление еще раз пе- редается блоку обработки SVC-прерываний. В ESB, заданном этим запросом, отмечено, что ожидаемое событие еще не насту- пило. Поэтому после перевода процесса Р1 в состояние блоки- ровки вызывается диспетчер (4), который передает управление процессу Р2 (5). В момент (6) Р2 выдает свой первый запрос ввода-вывода, предназначенный устройству 2 канала 2. Так как канал 2 свободен, запускается операция ввода-вывода, а управ- ление возвращается к Р2. В (8) Р2 должна ждать завершения ввода-вывода; так как этого еще не произошло, Р2 блокирует- ся. В (9), как и раньше, вызывается диспетчер. Однако в этот момент оба процесса блокированы, поэтому диспетчер пе- реводит ЦП в состояние простоя. Заметим, что оба канала ввода-вывода по-прежнему активны. Центральный процессор простаивает до момента (11), в ко- торый канал 1 завершает операцию ввода-вывода. Канал гене- рирует прерывание, в результате которого ЦП выводится из состояния простоя, а управление передается обработчику пре- рываний по. вводу-выводу. Определив, что операция заверши- лась нормально, обработчик прерываний выдает для соответ- ствующего ESB запрос SIGNAL (SVC 1). Тем самым управление передается блоку обработки SVC-прерываний (12). Процесс Р1, ожидающий изменения ESB, переводится в состояние готовности. Затем блок обработки* SVC-прерываний возвра- щает управление обработчику прерываний по вводу-выводу (13). Диспетчер, вызванный в момент (14), в момент (15) пе- редает управление процессу Р1. После завершения работы ка- нала 2 в (16) выполняется аналогичная последовательность операций; в результате процесс Р2 переводится в состояние го- товности. Поскольку ЦП в момент прерывания не простаивал, управление Р2 сразу не передается, а возвращается обработ- чиком прерываний по вводу-выводу процессу Р1. Процесс Р2 не получит управления до тех пор, пока Р1 не выдаст в (22) следующего запроса WAIT.
С. 2. Машинно-зависимые свойства операционных систем 343 Читателю, чтобы убедиться, что он хорошо понимает тонко- сти передачи управления при различных прерываниях, следует внимательно просмотреть весь пример до конца. Для этого бу- дет полезно обратиться к алгоритмам на рис. 6.8, 6.9, 6.13 и 6.14. В особенности обратите внимание на разнообразие видов одновременной работы ЦП и выполнения операций ввода-выво- да. Обеспечение гибкого следования заданий одно из основных преимуществ операционной системы с прерываниями. 6.2.4. Управление реальной памятью Любая операционная система, поддерживающая одновремен- ную работу более одного пользователя, должна обладать меха- низмом разделения центральной памяти между совместно вы- полняющимися процессами. Многие мультипрограммные систе- мы разбивают память на разделы (partitions) с выделением Длина (шестнадца*. Задание терянная > 1 A00G 2 14003 3 A80CI 4 4003 5 Е000 6 B0G3 7 СООО В D000 Рис. 6.16. Задания пользователей для примеров по распределению памяти. каждому процессу своего раздела. Размер и расположение раз- делов могут быть либо заранее заданы (разделы фиксирован- ного размера), либо назначаться динамически в процессе вы- полнения заданий (разделы переменного размера). Ниже рассматриваются некоторые способы управления раз- делением памяти. В примерах будет использована последова- тельность заданий, приведенная на рис. 6.16. Предполагается, что уровень мультипрограммирования (т. е. количество одно- временно выполняющихся заданий) ограничен только числом самих заданий, загружаемых в центральную память. На рис. 6.17 показано разбиение памяти на разделы фикси- рованного размера. Полный объем доступной памяти ЭВМ предполагается равным 50 000 байт; операционная система за- нимает первые 10000 байт. Заметим, что эти и другие разме- ры и адреса, используемые в данном разделе, даны в шестна- дцатеричном виде. Память, не занятая операционной системой, состоит из четырех разделов. Раздел 1 начинается с адреса 10 000 сразу за операционной системой и имеет длину 18 000 байт. Остальные разделы следуют в такам порядке: Разделы 2
344 Гл. 6. Операционные системы и 3 имеют по 10 000 байт каждый, а Раздел 4 — длину; 8000 байт. В простой схеме распределения с разделами фиксированного размера каждое входящее задание загружается в наименьший подходящий по объему раздел. Если размер раздела превосхо- дит размер задания, то оставшаяся внутри раздела память не используется. Система, имея вначале пустыми все четыре раз- дела, первым делом загрузит Задание 1 в Раздел 2. Затем За- дание 2 будет загружено в единственный достаточно большой Операцион- ная система Операцион- ная система Задание 5 Задание 6 Задание 3 Задание 4 Задание 5 Задание 6 Задание 7 9Ш Задание 4 М Оканчивается Оканчивается Оканчивается Задание 2 - . Задание 1 Задание 3 а-------—б — -----------► в------—----► г Рис. 6.17. Распределение памяти для заданий рис. 6.16 с использованием разделов фиксированного размера. для этого Раздел 1. Задания 3 и 4 загружаются в Разделы 3 и 4. После этого все разделы оказываются занятыми, поэтому больше заданий загрузить нельзя. Получившееся в итоге рас- пределение памяти показано на рис. 6.17а. Заштрихованные области на диаграмме соответствуют неиспользованной памяти. Однажды загруженное в раздел задание остается там до конца своего выполнения. После того как задание завершится, занимаемый им раздел вновь становится доступным для ис- пользования. На рис. 6.176 Задание 2 уже окончилось и в Раз- дел 1 было загружено Задание 5. На этом же рисунке показа- ны дальнейшие действия, осуществляемые по мере того, как оканчиваются остальные задания. Заметим, что сами разделы и их расположение остаются фиксированными вне зависимости от размеров занимающих их
6.2. Машинно-зависимые свойства операционных систем 345 заданий. Начальный выбор величины раздела в схеме с раз- делами фиксированного размера очень важен. Число больших разделов должно быть достаточным, чтобы длинные задания могли выполняться без слишком большой задержки. Однако если больших разделов слишком много, то при выполнении ко- ротких заданий значительное количество памяти расходуется впустую. Использование разделов фиксированного размера наи- 10000 1А000 2Е000 38800 ЗС800 4А800 Задание 5 Операцион- ная система Задание 1 Задание 2 Задание 3 Задание 4 10000 1А000 25000 31000 38800 ЗС800 4А800 Операцион- ная система Задание 6 Задание 7 Задание 4 Задание 5 Оканчивается Оканчивается Оканчивается Задание 2 л Задание 1 Задание 3 а---------------► 6----------------► в----------------> г Рис. 6.18. Распределение памяти для заданий рис. 6.16 с использованием раз4 делов переменного размера. более эффективно, когда размеры большинства заданий нахо- дятся в пределах конкретных объемов, а распределение разме< ров заданий меняется не часто. Это позволяет эффективно ис- пользовать доступную память посредством выделения набора разделов ожидаемому множеству заданий. На рис. 6.18 демонстрируется выполнение того же множе-' ства заданий при использовании разделов переменного размера. Для каждого загружаемого задания создается новый раздел о размерами, соответствующими заданию. После окончания зада- ния отведенная ему память освобождается и может быть ис- пользована при распределении других разделов. Вначале вся память (кроме той, что отведена операционной системе) не распределена, так как заранее нет определенных разделов. Раздел для Задания 1 создается при его загрузке. Предположим, что этот раздел расположен непосредственно за
В46 Гл. 6. Операционные системы операционной системой. Затем Заданию 2 отводится раздел, следующий сразу за Заданием 1, и т. д. Объем свободной па- мяти, оставшейся за Заданием 5, для загрузки очередного за- дания уже недостаточен. Когда завершается Задание 2, его раздел освобождается, и новый Раздел отводится Заданию 6. Как показано на рис. 6.186, этот новый раздел занимает часть памяти, которая отводилась Заданию 2. Остаток от прежнего раздела Зада- ния 2 остается свободным. Теперь имеются две несмежные сво- бодные области памяти; однако ни одна из них не велика на- столько, чтобы вместить еще одно задание. На рис. 6.18в и г показано, как происходит освобождение и распределение памя- ти по мере того, как оканчиваются другие задания. При использовании разделов переменного размера нет на- добности выбирать размер раздела заранее. Однако операцион- ной системе, которая отслеживает, какие области памяти уже распределены, а какие свободны, приходится проделывать боль- шую работу. Обычно это делается системой при помощи под- держиваемого ею связаного списка свободных областей. Этот список просматривается при выделении нового раздела, который размещается либо в первой (первое подходящее размещение), либо в наименьшей (наиболее подходящее размещение) подхо- дящей для него свободной области. Когда раздел освобождает- ся, отведенная ему память объединяется со всеми смежными свободными областями и заносится в список. Подробное обсуж- дение и сравнение алгоритмов распределения памяти можно найти в Стендиш [1980]. Вне зависимости от используемого способа создания разде- лов необходимо, чтобы операционная система и аппаратные средства обеспечивали защиту памяти. При выполнении зада- ния в одном разделе недопустимо, чтобы оно изменяло ячейки памяти другого раздела или операционной системы. В некото- рых системах разрешено чтение данных из любой области па- мяти, а запись — только внутри отведенного заданию раздела. В других системах и чтение, и запись допускаются только в пределах собственного раздела задания. Для эффективной защиты памяти необходима и аппаратная поддержка. Можно, например, ввести пару граничных регист- ров, в которых будут содержаться начальный и конечный ад- реса раздела задания. Эти регистры не доступны непосредст- венно программам пользователя, и могут быть использованы, только если ЦП находится в режиме супервизора. Операцион- ная система устанавливает граничные регистры, когда пользо- вательскому заданию назначается раздел. Во время операций контекстного переключения, аналогичных тем, что вызваны командой EPS или прерыванием, содержимое этих регистров автоматически сохраняется. Таким образом, граничные регист-
6.2. Машинно-зависимые свойства операционных систем 347 ры всегда содержат адреса начала и конца раздела, отведенно- го текущему активному процессу. При любом обращении к па- мяти аппаратура автоматически сверяет адрес, по которому идет обращение, с граничными регистрами. Если адрес находится вне раздела текущего задания, обращение к памяти не производится и генерируется программное пре- рывание. В УУМ/ДС используется и другой способ защиты памяти. Каждому 800-байтовому (в шестнадцатеричной системе счисле- ния) блоку памяти ставится в соответствие 4-битовый ключ за- щиты памяти. Эти ключи могут быть установлены операцион- ной системой при помощи привилегированной команды SSK (Set Storage Key — Установи ключ памяти). Каждый пользова- тельский процесс имеет поставленный ему в соответствие 4-би- товый идентификатор процесса, который хранится в поле ID слова состояния SW. Когда заданию выделяется некоторый раздел, операционная система присваивает ключам всех блоков памяти внутри раздела значение идентификатора процесса этого задания. При каждом обращении пользовательской про- граммы к памяти аппаратура автоматически сравнивает иденти- фикатор процесса из SW с ключом защиты адресуемого блока памяти. Если значения этих двух полей не совпадают, то обра- щения к памяти не происходит и генерируется программное прерывание. Однако если ЦП находится в режиме супервизора, то этой проверки не производится; операционная система мо- жет обращаться к любой ячейке памяти. Единой проблемой для всех общецелевых способов дина- мического распределения является фрагментация памяти. Фраг- ментация имеет место, когда доступная свободная память раз- бита на несколько несмежных блоков, каждый из которых слишком мал для использования. Рассмотрим, например, рис. 6.18в. Чтобы разместить Задание 7, в целом имеется более чем достаточно свободной памяти; однако из-за того, что нет ни одного свободного блока достаточно большого размера, оно не может быть загружено. На рис. 6.19 показано одно из возможных решений этой проблемы: использование перемещаемых разделов (relocatable partitions). После окончания каждого задания оставшиеся раз- делы передвигаются как можно дальше к одному концу памяти В результате этого вся доступная свободная память собира* ется в один общий блок, который больше подходит для распре* деления новых разделов. Как показано на рис. 6.19, этот способ может привести к более эффективному использованию памяти по сравнению с тем, что достигается с помощью неперемещае- мых разделов. Однако копирование заданий из одного места памяти в другое может потребовать значительного количества
348 Гл. 6. Операционные системы времени. Этот недостаток часто перевешивает преимущества |усовершенствованного использования памяти. При работе с перемещаемыми разделами возникают пробле- мы и с переместимостью программ. Рассмотрим, например, про- грамму, приведенную на рис. 6.20а. Команда STA имеет рас- ширенный формат, поэтому она в оттранслированном виде со- держит истинный адрес 08108. При использовании методов, Операцией- Операцией- Операцион- Операцион- 10000 ная система 10000 ная система 10000 ная система 10000 14000 ная система Задание 1 Задание 4 1А000 1А000 Задание 1 1А800 1Е800 Задание 3 Задание 5 Задание 3 Задание 4 22000 Задание 2 24800 28800 Задание 5 Задание 4 Задание 6 2Е000 2С8ОО 2D000 Задание 3 36800 Задание 5 37800 Задание 6 Задание 7 38800 ЗС800 39000 Задание 4 Задание 6 Задание 7 41800 Задание 8 Задание 5 43800 Задание 7 46000 4А800 4D800 Я Оканчивается Оканчивается Оканчивается Задание 2 Задание 1 Задание 3 а । г к > о ► в ► г Рис. 6.19. Распределение памяти для заданий рис. 6.16 с использованием перемещаемых разделов. описанных в гл. 2 и 3, это адресное поле будет отмечено ас- семблером. Чтобы получить истинный адрес, по которому за- гружена программа, значение адресного поля модифицируется загрузчиком. Например, если начальная загрузка РЗ осуще- ствлена по истинному адресу 2Е000, то адресное поле команды STA, как показано на рис. 6.206, будет изменено на 36108. Предположим теперь, что раздел, содержащий РЗ, будет (Начинаться не с адреса 2Е000, как на рис. 6.19а, а с адреса TA000, как на рис. 6.196. Тогда адрес в команде STA будет неверным. Действительно, он будет указывать на ячейку памя- ти, которая является частью раздела, отведенного другому за- данию. Аналогичная проблема возникнет, если РЗ загрузит в базовый регистр адрес некоторой своей части или создаст структуру данных, использующую указатель адреса. После на- чальной загрузки программы все перемещаемые значения опре-
6.2. Машинно-зависимые свойства операционных систем 349 Адрес Исходный оператор 0000 РЗ START О 1840 +STA BUFF2 8108 BUFF2 .. . э END а 0F1Q8108 Объектный код 6 Регистр перемещений а г Рис. 6.20. Использование регистра перемещений при вычислении адреса. делены ассемблером, что упрощает перемещение программы. Однако во время своего выполнения программа может исполь» зовать регистры и ячейки памяти произвольным образом. В об- щем случае операционная система не в состоянии определить, какие значения являются адресами, а какие другими типами данных. Таким образом, перемещение программы во времянвы- полнения осуществить намного сложнее, чем после начальной загрузки.
350 Гл 6 Операционные системы Применение перемещаемых разделов на практике требует некоторой аппаратной поддержки. Один из обычно используе- мых для этого способов показан на рис. 6.20в. В этом случае существует специальный регистр перемещения, устанавливае- мый операционной системой и содержащий начальный адрес текущей активной программы. Во время операций контекстного переключения содержимое этого регистра автоматически сохра- няется или восстанавливается, а при перемещении программы на новое место его значение изменяется операционной системой. При любом обращении программы пользователя к памяти зна- чение, содержащееся в регистре перемещения, автоматически складывается с адресом. Например, команда STA на рис. 6.20в обращается к адресу 08108; однако, учитывая регистр пере- мещения, истинный адрес, к которому осуществляется обраще- ние, равен 36108. Если начало программы РЗ было перенесено на адрес 1А000, как показано на рис. 6.20г, то операционная система изменит значение регистра перемещений для РЗ на 1А000. Таким образом, адрес, к которому действительно обра- щается команда STA, будет равен 22108. Важно понять, что регистр перемещения управляется опера- ционной системой; пользовательской программе он не доступен. Таким образом, этот регистр в корне отличается от задаваемых программно базовых регистров, имеющихся в4 УУМ/ДС, Sy- stem/370 и многих других ЭВМ. Регистр перемещения автома- тически включается каждый раз, когда программа обращается к какой-нибудь области памяти; этим достигается тот же эф- фект, что и при фактической загрузке каждой программы с истинного адреса 00000. И в самом деле, на многих ЭВМ для программы пользователя не существует прямого способа опре- делить ее реальное месторасположение в памяти. Таким обра- зом, этот тип перемещений применим к адресам, находящимся в указателях структур данных, к значениям в базовых регист- рах, равно как и к адресам в командах. Заметим также, что подобное автоматическое перемещение, выполняемое аппарату- рой, полностью исключает необходимость перемещения про- грамм загрузчиком. В этом разделе были рассмотрены методы распределения программных разделов с заранее определенными размерами, В некоторых операционных системах программам пользователей во время их выполнения разрешается динамически запрашивать дополнительную память. Дополнительно выделяемая память не обязательно должна быть смежной с первоначально назначен- ным разделом. Подобное динамическое распределение памяти обычно осуществляется методами, схожими с теми, что исполь- зовались при управлении памятью для структур данных. Хорошее изложение этих методов можно найти в Стендиш 11980],
6.2 Машинно-зависимые свойства операционных систем 351 6.2.5. Управление виртуальной памятью Виртуальным (virtual) называется ресурс, который пользой вательской программе представляется обладающим свойствами отличными от тех, что он в действительности имеет. Рассмот* рим, например, рис. 6.21. Программе разрешено использовать большую виртуальную память, называемую иногда виртуаль- ным адресным пространством. Его объем может даже превосхо-. Виртуальная память Рис. 6.21. Основная концепция виртуальной памяти* дить всю доступную реальную память на ЭВМ. Содержимое виртуальной памяти, используемой программой, хранится на некотором внешнем устройстве (внешней памяти). По необхо- димости части этой виртуальной памяти отображаются в реаль- ную память. Ни о внешней памяти, ни о ее отображении в ре-: альную память программа ничего не знает. Она написана так, как будто бы виртуальная память существует в действитель- ности. В этом разделе мы рассмотрим один из общих методов реа- лизации виртуальной памяти — размещение страниц по запросу (demand paging). Ссылки на литературу, содержащую описания других видов виртуальной памяти, приведены в конце раздела, В системе с размещением страниц по запросу виртуальная память процесса разделена на страницы некоторой фиксирован- ной длины. Реальная память ЭВМ разделена на страничные кадры (page frames) той же длины, что и страницы. Любая страница любого процесса потенциально может быть загружена в любой страничный кадр реальной памяти. Отображение страниц
352 Гл. 6. Операционные системы . Объектный Ддрво исходный {итератор ход > QQQOQQ РЗ STAKT С O0QI03 +JEQ SKIPX 33X00420 : 000420 SKIP1 +LPA BUFFI 03106FFA СтМ 001840 SKIPS +STA BUFF2 OF108103 .стр, 2 002020 002024 *Л.Т SKIPS ЗВ104А00 J SKIPS 3F281G 004А0О 004А20 SKIPS LDX #8 +STA BUFFIS 050008 QFSQOFFA СТМ Рис. 6.22. Программа, иллюстрирующая размещение страниц по запросу.' в страничные кадры описывается таблицей отображения страниц (РМТ— Page Map Table); для каждого процесса в системе имеется одна РМТ. Таблица отображения страниц ис- пользуется аппаратурой для преобразования адресов виртуаль- ной памяти программы в соответствующие адреса реальной па- мяти. Процесс преобразования похож на использование регист- ра перемещений, рассмотренного в предыдущем разделе. Одна- ко здесь для каждой страницы в РМТ имеется свое значение.
6.2. Машинно-зависимые свойства операционных систем 353 стр. 7 < Стр. 8 Стр. 9 Стр. А Адрес Исходный оператор 006FFA 0G8108 BUFFI RESW BUFF2 RESW Объектный код Стр- Ъ < 00А800 END Рис. 6.22. Продолжение. Такой перевод виртуальных адресов в реальные называется динамическим преобразованием адресов. Рассмотренные понятия иллюстрируются программой на рис. 6.22. Программа разбита на страницы длиной 1000 (шест- надцатеричный) байт. Виртуальные адреса с 0000 по 0FFF принадлежат Странице 0, адреса с 1000 по 1FFF — Страни- це 1 и т. д. Когда начинается выполнение программы, операци- онная система загружает Страницу 0, содержащую первую ис- полняемую команду, в некоторый страничный кадр реальной памяти. Остальные страницы загружаются в память по мере надобности. Процессы динамического преобразования адресов и загрузки страниц иллюстрируются рис. 6.23а. Страница 0 программы загружена в страничный кадр 1D (т. е. в адреса реальной па- мяти с 1D000 по 1DFFF). Рассмотрим сначала команду JEQ, находящуюся по виртуальному адресу 0103. Адрес операнда 12 Зак. 792
(Виртуальный адресу Рис. 6t23* Примеры динамического Гл. 6. Операционные системы преобразования адресов и размещения страниц по запросу.
6.2. Машинно-зависимые свойства операционных систем 355 этой команды — виртуальный адрес 0420. Для простоты в при- мере использован формат команды, допускающий прямую ад- ресацию. Адрес операнда 0420 находится на Странице 0 и имеет смещение 420 от ее начала. В таблице отображения страниц показано, что Страница 0 программы загружена в страничный кадр 1D (начинающийся с адреса 1D000). Таким образом, реальный адрес, вычисленный при динамическом пре- образовании адресов, равен 1D420. Рассмотрим далее команду LDA с виртуальным адресом 0420. Ее операнд расположен по виртуальному адресу 6FFA (Страница 6, смещение FFA). Однако эта страница в реаль- ную память еще не загружена, поэтому аппаратные средства динамического преобразования адресов вычислить реальный адрес не могут. Вместо этого они генерируют специальный тип программного прерывания, называемого прерыванием по отсут~ ствию страницы или страничным прерыванием (рис. 6.236) 4 В ответ на это стандартная программа обработки прерываний, которая будет рассмотрена позже, загружает требуемую стра- ницу в некоторый страничный кадр. Предположим, что его но- мер — 29. После этого заново выполняется команда, вызвавшая прерывание. На этот раз, как показано на рис. 6.23в, динами- ческое преобразование адреса проходит успешно. Остальные страницы программы загружаются в случае не- обходимости подобным же образом. Предцоложим, что на рис. 6.22 показаны все команды перехода (JUMP) в програм- ме, равно как и все команды, операнды которых находятся на других страницах. Когда управление передается от последней команды на Странице 0 первой на Странице 1, операция вы- борки команды вызывает страничное' прерывание, в результате которого загружается Страница 1. Команда STA с виртуаль- ным адресом 1840 вызывает загрузку Страницы 8. Затем в ре- зультате выполнения цикла выборки команд загружается Стра- ница 2, так же как перед этим Страница 1. Рассмотрим теперь две команды JUMP с адресами 2020 и 2024. Осуществление первого из этих переходов (т. е. условие «меньше чем » истин- но) вызывает загрузку Страницы 4; иначе же Страница 4 оста- ется незагруженной. В последнем случае команда безусловного перехода с адресом 2024 передает управление на ячейку Стра- ницы 1 (которая уже загружена). После передачи управления на Страницу 4 выполняется команда STA ,С адресом 4А20. Ею задается адрес 6FFA операнда с индексное адресацией. Пред- положим, что содержимое индексного регистра равно 8. Тогда целевой адрес равен 7002 и выполнение команды приводит к загрузке Страницы 7. На рис. 6.24 показана ситуация, возникшая после только что описанных событий. По таблице отображение страниц можно судить, что Страницы 0, 1, 2, 4, 6, 7 и 8 загружены; в ней 12*
356 Гл. 6. Операционные системы даны также соответствующие номера страничных кадров. Для каждой программы в системе имеется аналогичная таблица. Заметим, что в ней указаны также и страницы, которые с мо- мента загрузки были модифицированы (здесь — Страницы 7 и 8). Эта информация используется программой обработки стра- ничных прерываний при выталкивании страницы из памяти. Стрй- ъичнын кадр Реальная память Рис. 6.24. Отображение виртуальное — реальное с использованием таблиц ото- бражения страниц. Рис. 6.25 суммирует продемонстрированные выше функции преобразования адресов и размещения страниц по запросу. На рис. 6.25а дан алгоритм динамического преобразования адресов. Напомним, что он реализован непосредственно аппаратными средствами машины. Если динамическое преобразование адре- сов не может быть завершено потому, что требуемой страницы в памяти нет, происходит страничное прерывание. На рис. 6.256 показана программа обработки прерывания, вызванного отсутствием страницы. Операционная система со- держит таблицу, описывающую состояние всех страничных кад- ров. На первом шаге обработки прерывания таблица просмат-
6.2. Машинно-зависимые свойства операционных систем 357 ривается в поисках свободного страничного кадра. Если он найден, требуемая страница может быть загружена немедлен- но. В противном случае страница, находящаяся в памяти, дол- жна быть вытолкнута, чтобы освободить место для той, кото* procedure DAT (реализуемая аппаратно} разложить виртуальный адрес на (номер страницы, смещение} найти вхождение для этой страницы в РМТ if страница находится в памяти then begin объединить (адрес страничного кадра, смзи|еяие) для создания страничного кадра if это команда "store* then пометить в РМТ, что страница модифицирована end (if страница находится в памяти) else вызвать страничное прерывание а procedure PAGEFAULT (реализуется как часть операционной системы} сохранить состояние процесса из рабочей области прерывания пометить процесс как Блокированный if имеется свободный страничный кадр then begin выбрать свободный страничный кадр пометить выбранный кадр в таблице страничных кадров как Занятый разрешить все прерывания командой LPS end (if имеется свободный страничный кадр} felse begin выбрать страницу для выталкивания пометить выбранный кадр в таблице страничных кадров как Занятый разрешить все прерывания командой LPS if выбранная страница была модифицирована then begirt обновить РМТ и таблицу страничных кадров выдать запрос на ввод-вывод, чтобы переписать страницу во внешнюю память ждать завершения операции записи end (if страница была модифицирована} end (if нет свободного страничного кадра} выдать запрос на ввод-вывод для чтения страницы в выбранный страничный кадр ожидать завершения операции чтения обновить РМТ и таблицу страничных кадров пометить процесс, как Готовый восстановить состояние пользовательского процесса, вызвавшего страничное прерывание б Рис. 6.25. Алгоритмы динамического преобразования адресов и о бра бона! страничных прерываний. рую требуется загрузить. Если выталкиваемая страница с мо< мента загрузки была модифицирована, то ее новая версия дол- жна быть переписана во внешнюю память (backing store) * Если нет, то она может быть просто уничтожена. Программа обработки страничных прерываний, показанная на рис. 6.256, требует выполнения по крайней мере одной
358 Гл. 6. Операционные системы операции ввода-вывода. Поэтому для ее работы нужно намного больше времени, чем для любого другого обработчика прерыва- ний из рассмотренных нами выше. Часто бывает нежелательно закрывать прерывания на длительное время. Ясно, например, что предпочтительнее во время страничного обмена дать воз- можность использовать ЦП другим программам. Для управления подобным мультипрограммированием требуется, чтобы прерыва- ния были разрешены. Поэтому при прерывании по отсутствию страниц сначала определяется, какое действие требуется вы- полнить, и сохраняется информация о состоянии прерванного процесса, а затем в течение оставшегося времени обработки разрешается функционирование системы прерываний. Сложнее производится выбор обработчиком прерываний страничного кадра для записи требуемой страницы. Кадр /го- мечается как занятый, чтобы он не был выбран в следующий раз. Для того чтобы информация о состоянии не была унич- тожена во время следующего прерывания, обработчик прерыва- ваний берет ее из рабочей области прерывания программы и сохраняет в некотором месте, отведенном данному процессу. Затем, загрузив слово состояния, отличающееся от текущего SW тем, что в нем установлены в «1» все биты MASK, обра- ботчик прерываний включает систему прерываний. Остальная часть программы обработки прерываний функционирует так же, как и пользовательский процесс: для инициирования операции ввода-вывода и ожидания результатов их выполнения она дела- ет SVC-запросы. После завершения операции обмена страниц обработчик прерываний, используя сохраненную информацию о состоянии, снова передает управление на команду, вызвавшую прерывание. В алгоритме, приведенном на рис. 6.256, не учтены некото- рые важные моменты. Самый очевидный из них — какую стра- ницу вытолкнуть из памяти. В некоторых системах хранятся записи, фиксирующие последнее обращение к каждой странице; выталкивается та, что дольше не использовалась. Это так на- зываемый метод выталкивания дольше всего не использовав- шейся страницы (LRU— Least Recently Used), Так как на- кладные расходы при таком способе хранения записей могут быть высокими, для LRU используются простейшие виды ап- проксимации. В других системах делаются попытки определить множество наиболее часто используемых страниц опрашивае- мого процесса (так называемое рабочее множество процесса). В подобных системах выталкивание страницы производятся та- ким образом, чтобы каждый процесс всегда имел в памяти свое рабочее множество. Обсуждение и развитие различных стратегий выталкивания страниц можно найти в Дейтел [1984]. Другой вопрос, оставленный без ответа, связан с реализаци- ей самих страничных таблиц, Одно из возможных решений за-
6.2. Машинно-зависимые свойства операционных систем 359 ключается в их реализации в виде массивов в центральной па- мяти. В качестве указателя на начало РМТ текущего активного процесса используется некоторый регистр, устанавливаемый операционной системой. Этот метод может оказаться неэффек- тивным, потому что для каждого преобразования адресов требу- ется быстрый доступ к памяти. Однако в некоторых системах этот способ используется в сочетании с высокоскоростным бу- фером, служащим для улучшения среднего времени доступа. Другая возможность заключается в реализации таблиц отобра- жения страниц при помощи специальной высокоскоростной ас- социативной памяти. Такой способ очень эффективен, но может оказаться слишком дорогим для систем с большой реальной памятью. Дальнейшее обсуждение этих и других способов реа- лизации РМТ может быть найдено в Дейтел {1984]. Системы с размещением страниц по запросу избегают нера- ционального использования памяти в основном при помощи фрагментации, часто связанной со способами организации раз- делов. Память можно экономить и другими способами. Напри- мер, стараются не загружать те части программы, которые в те- чение определенного времени выполнения не используются. Од- нако системы с размещением страниц по запросу имеют и другие уязвимые места. Например, предположим, что обращение к слову в центральной памяти занимает 1 мкс, а считывание страницы из внешней памяти — в среднем 10 мс (10000 мкс). Предположим также, что в среднем для всех заданий в систе- ме только 1 из 100 обращений к виртуальной памяти вызывает страничное прерывание. Даже при такой очевидно малой вели- чине страничных прерываний система хорошо работать не бу- дет. На каждые 100 обращений (требующих 100 мкс) система потратит 10 000 мкс на считывание страниц из внешней памяти. Таким образом, вычислительная система тратит приблизительно 99 % своего времени на свопинг страниц и только 1 % на по- лезную работу. Этот полный разлад в работе называется про- буксовкой (thrashing). Во избежание пробуксовки в только что описанной операции необходимо, чтобы число страничных прерываний было намного меньше (возможно, порядка одного прерывания на каждые 10 000 обращений к памяти). На первый взгляд может пока- заться, что для достижения приемлемой производительности все программные страницы должны находиться в памяти. Однако это необязательно. Благодаря свойству, называемому локальностью ссылок, которое можно наблюдать в реальных программах, обращения к памяти не распределены случайно по всему виртуальному адресному пространству программы. Вме- сто этого они имеют тенденцию к группированию в адресном пространстве, как показано на рис. 6.26а. Это происходит из-за таких общих свойств программ, как последовательное
369 Гл. 6. Операционные системы выполнение команды, компактное кодирование циклов, последо- вательная обработка структур данных и т. п. Благодаря локальности ссылок можно достичь приемлемо малого числа страничных прерываний, не храня все адресное пространство программы в реальной памяти. На рис. 6.266 для гипотетической программы величина страничных прерываний дана как функция от числа страниц программы, содержащихся в памяти. Масштаб этой кривой для разных программ сильно Рис. 6.26. а — Локализованные обращения к памяти (каждая точка — это ячейка памяти, к которой было осуществлено обращение в данный момент времени в процессе выполнения программы); б — Эффект локализации ссылок в зависимости от числа страничных прерываний. варьируется, однако ее поведение типично для многих реаль- ных программ. Часто, как и в данном примере, существует критическая точка W. Если в памяти содержится меньше W страниц, наблюдается пробуксовка. Если в памяти находятся W страниц или больше, то производительность будет удовлет- ворительной. Значение этой критической точки W и является размером рабочего множества страниц программы, о котором говорилось ранее. Дальнейшее обсуждение результатов, связан- ных с размерами рабочего множества и пробуксовкой, можно найти в Дейтел [1984] и Лорин [1981]. Размещение страниц по запросу дает еще один пример от- ложенной связи (delayed binding) соответствие между адресами виртуальной и реальной памяти не устанавливается до момента обращения к памяти. Такая задержка требует дополнительных расходов (на динамическое преобразование адресов, считывание
6.3. Машинно-независимые свойства операционных систем 361 страниц и т. п.). Однако для программиста она может оказать- ся более удобной, а также обеспечить эффективное использова- ние реальной памяти. Можно сравнить эти замечания с теми, что были сделаны в предыдущих примерах по отложенным свя- зям (разд. 3.4.3, 5.3.1 и 5.4.2). Выше была рассмотрена реализация виртуальной памяти при помощи размещения страниц по запросу. Другой тип вир- туальной памяти можно реализовать посредством сегментной организации памяти (сегментации). В системе с сегментной организацией виртуальной памяти адрес состоит из номера сег- мента и величины смещения внутри него. Принципы отображе- ния и динамического преобразования адресов аналогичны ранее рассмотренным. Однако сегменты могут иметь произвольную длину (в отличие от страниц, длина которых в каждой системе фиксирована). Кроме того, сегменты обычно соответствуют ло- гическим блокам программы, таким как процедуры или обла« сти данных (в отличие от страниц, которые обычно являются результатом произвольного разбиения адресного пространства); Это дает возможность связать такие атрибуты защиты, как только чтение или только выполнение, с конкретными сегмент тами. Возможно также разделение сегментов между различны-» ми заданиями пользователей. Сегментная организация памяти часто используется вместе с размещением страниц по запросу; При этом имеет место двухуровневая процедура отображения и преобразования адресов. Дальнейшую информацию по сегмент- ной организации памяти и ее реализации можно найти в Дей- тел [1984], Лорин [1981] и Мэдник [1974]. 6.3. Машинно-независимые свойства операционных систем В этом разделе будут кратко рассмотрены некоторые общие функции операционных систем, непосредственно не зависящие от архитектуры машины, на которой они функционируют. Эти свойства могут быть реализованы на более высоком уровне по сравнению с рассмотренными ранее. Поэтому они не столь фундаментальны с точки зрения основного назначения опера- ционных систем, как вопросы аппаратной поддержки, рассмот- ренные в предыдущем разделе. Объем книги не позволяет об- суждать их в деталях. Для читателей, которые захотят больше узнать о вопросах, изложенных ниже, приводятся ссылки на литературу. В разд. 6.2.3 были рассмотрены возможные способы управ- ления операциями ввода-вывода. В разд. 6.3.1 изучается ана- логичный вопрос для более высокого уровня: управлений
362 Гл. 6. Операционные системы логическими файлами и работа с ними. В разд 6.3.2 обсуждает- ся проблема планирования, т. е. выбора из заданий пользова- телей кандидатов на обслуживание, производимое на более низком уровне, о чем говорилось ранее. В разд. 6.3.3 рассматривается основной объект распределе- ния ресурсов, осуществляемого операционной системой, и воз- никающие при этом сложности. Наконец, в разд. 6.3.4 вводятся такие важные понятия, как защита и безопасность операцион- ных систем. €.3.1. Работа с файлами Данный раздел посвящен некоторым функциям, выполняе- мым типичными операционными системами в процессе управ- ления и работы с файлами. В большинстве систем программы пользователей имеют возможность запрашивать ввод-вывод при помощи средств, описанных в разд. 6.2 (канальных программ и SVC-запросов). Однако это достаточно неудобно, так как про- граммисту должны быть известны коды канальных команд и их форматы, а программе пользователя — номера каналов, устройств и, возможно, реальный адрес требуемой записи, если используется устройство с прямым доступом (например, диск). Кроме того, нужно отслеживать окончание ввода-вывода, вы- полнять буферизацию и объединение в блоки, о чем будет ска- зано ниже. Функция управления файлами, осуществляемая операцион- ной системой, является промежуточным звеном между програм- мой пользователя и супервизором ввода-вывода (рис. 6.27), Программа пользователя на логическом уровне с помощью имен файлов, ключей и т. п. делает запросы, например «прочи- тать следующую запись из файла F». Программа управления файлами реализует метод доступа, транслируя логические за- просы в физические запросы на ввод-вывод (т. е. канальные программы), и передает их супервизору, действующему при управлении операцией ввода-вывода (разд. 6.2.3). Чтобы перевести логические программные запросы в каналь- ные программы, система управления файлами должна иметь информацию о расположении и структуре файлов. Эту инфор- мацию она получает из структуры данных, называемой катало- гом, и файловых информационных таблиц. В действительности термины, используемые для обозначения подобных структур, так же как и их форматы и содержание, в разных операцион- ных системах различны. Каталог устанавливает соответствие логических имен файлов с их физическим местонахождением и может предоставить о файлах некоторую общую информацию, В файловой информационной таблице содержится дополнитель- ная информация^ например об организации файлов, длине
6.3. Машинно-независимые свойства операционных систем 363 записи и форматах, о способе индексирования, если оно имеет- ся. Чтобы начать работу с файлом, система просматривает ка- талог и определяет местонахождение соответствующей файло- вой информационной таблицы. Система также может создать буфера для размещения в них блоков файлов, которые будут прочитаны или записаны. Эта процедура называется откры- тием файла. После окончания работы с файлом буферные и другие рабочие области и указатели уничтожаются. Такая процедура называется закрытием файла. Рис. 6.27. Использование программы управления файлами для ввода-вывода. Одной из наиболее важных функций управления файлами является автоматическое выполнение операций буферизации и объединения в блоки читаемых и записываемых файлов. На ,рис. 6.28 эти операции иллюстрируются последовательно вводи- мым файлом. Предполагается, что чтение записей осуществля- ется программой пользователя последовательно с начала файла и до его конца. Логически файл состоит из записей длиной 1024 байт; физически, однако, файл образован 8192-байтовыми блоками, каждый из которых содержит 8 логических записей. Этот вид объединения записей в блоки обычно осуществляется конкретными типами запоминающих устройств в целях эконо- мии времени обработки и пространства памяти. Подробнее об этом говорится в Лумис [1983]*
364 Гл. 6. Операционные системы На рис. 6.28а показана ситуация, сложившаяся после того, как файл был открыт и программа пользователя осуществила свой первый запрос на чтение записи. Система управления фай- лами уже выдала запрос на ввод-вывод для чтения в буфер В1 первого блока файла и должна ждать завершения этой опе- рации, прежде чем сможет передать требуемую запись поль- зователю. На рис. 6.286 первый блок прочитан. Он находится в буфере В1 и содержит логические записи с номерами от 1 Рис. 6.28. Блокирование и буферизация последовательного файла. до 8. Теперь система управления файлами может передать тре- буемую запись программе пользователя, для чего указатель Р устанавливается на первую логическую запись. Для чтения в буфер В2 второго блока файла система управления файлами выдает второй физический запрос на ввод-вывод. Когда программа пользователя в следующий раз делает за- прос на чтение записи, уже нет необходимости ждать каких- либо действий по вводу-выводу. Система просто передвигает указатель Р на логическую запись 2 и возвращает управление пользователю. Эта операция показана на рис. 6.28в. Заметим, что физическая операция ввода-вывода по чтению второго бло- ка в буфер В2 все еще выполняется. Те же действия повтори-
6.3. Машинно-независимые свойства операционных систем 365 ются и для других логических записей первого блока (рис. 6.28г). Если программа пользователя осуществляет запрос на чте- ние 9-й записи до завершения операции ввода-вывода для бло* ка, системе управления файлами снова приходится заставлять программу ждать. После того как второй блок прочитан, ука* затель Р переводится на первую запись буфера В2. После этого система управления файлами выдает запрос на ввод-вы* вод для чтения в буфер В1 третьего блока и процесс продол* жается подобно тому, как было описано выше. Заметим, чтб использование двух буферных областей позволяет совмещать работу с одним блоком и чтение другого. Подобный способ, ча- сто называемый двойной буферизацией, широко используется при обмене последовательными файлами. В предыдущем примере программа пользователя осущест- вляла просто серию запросов на чтение записей, ничего не зная о буферизации и деталях выполнения физических запросов на ввод-вывод. Сравните это с программой на рис. 6.11, которая выполняет схожую функцию буферизации, имея дело непосред- ственно с супервизором ввода-вывода. Ясно, что использование системы управления файлами сильно упрощает программу, об* легчает ее написание и, следовательно, уменьшает число воз- можных ошибок. Не требуется и повторение во многих про* граммах одних и тех же кодов. Программы управления файлами выполняют также много других функций: распределение пространства на внешних запо- минающих устройствах, реализацию правил управления досту- пом к файлам и их использованием. Дальнейшее обсуждение этих вопросов можно найти в Дейтел [1984] и Мэдник [1974]; 6.3.2. Планирование заданий Планирование заданий.— это выбор заданий пользователей для выполнения. В однопрограммной системе оно сводится к определению порядка выполнения заданий. В мультипрограм- мной системе планировщик определяет порядок входа заданий в решение. На рис. 6.29а показана типичная двухуровневая система планирования в мультипрограммной системе. Задания, посту! пающие в систему, помещаются в очередь входных заданий, из которой они затем выбираются планировщиком заданий. Выб^1 ранные задания становятся активными; это означает, что он® начинают принимать участие в операции планирования процесс* сов, описанной в разд. 6.2.2. Двухуровневая процедура вводится в целях ограничения уровня мультипрограммирования, т. е< числа заданий пользователей, между которыми происходит раз; деление ЦП и других системных ресурсов, Это необходимо для.
366 Гл. 6. Операционные системы поддержания эффективной работы мультипрограммной системы. Если система пытается выполнять одновременно слишком мно- го заданий, перекрытие при управлении ресурсами становится слишком большим, а количество доступных каждому заданию ресурсов — слишком малым. В результате производительность системы снижается. В только что описанной схеме планировщик заданий исполь- зуется в качестве инструмента для поддержания требуемого уровня мультипрограммирования. Однако этот идеальный уро- 6 Рис. 6.29. а — Двухуровневая система планирования и б — трехуровневая система планирования. вень может меняться в зависимости от характера выполняю- щихся заданий. Рассмотрим, например, систему, в которой для управления памятью применяется стратегия размещения стра- ниц по запросу. Количество заданий, одновременно использую- щих реальную память, по существу, не ограничено. Выполнять- ся может любое задание, имеющее по крайней мере одну стра- ницу. Однако, когда в памяти у задания отсутствует некоторое критическое число страниц, начинается пробуксовка, и от этого страдает производительность всей системы. К сожалению, труд- но заранее определить то количество выделяемых заданию страниц, которое позволит избежать пробуксовки. Вдобавок в течение выполнения программы оно может сильно меняться, по- этому во время работы системы может меняться и требуемый уровень мультипрограммирования. Если входная очередь не пуста, уровень мультипрограммиро- вания достаточно просто можно увеличить, обратившись к пла- нировщику заданий. Труднее бывает его уменьшить, например для.того, чтобы остановить пробуксовку. Это обычно достигает-
6.3. Машинно-независимые свойства операционных систем 367 ся при помощи процедуры трехуровневого планирования зада- ний, которая показана на рис. 6.296. Планировщик заданий и планировщик процессов (т. е. диспетчер) работают, как и ра- нее. Однако существует также планировщик промежуточного уровня, управляющий производительностью системы и, если требуется, регулирующий уровень мультипрограммирования. Если он слишком высок, промежуточный планировщик понижа- ет его, приостанавливая или свертывая одно или несколько за- даний. Если уровень мультипрограммирования слишком низок, промежуточный планировщик возобновляет работу приостанов- ленных заданий или обращается к планировщику заданий для того, чтобы активизировать новые. Промежуточные планиров- щики могут использоваться и для регулирования приоритета диспетчеризации активных заданий, основываясь на наблюде- нии за ними в процессе выполнения. Вся система планирования обычно базируется на системе приоритетов, предназначенных для достижения определенных целей. Первой целью может быть, например, получение макси- мальной пропускной способности системы, т. е. выполнение наи- большей вычислительной работы за кратчайшее время. Ясно, что для этого требуется эффективное использование всех си- стемных ресурсов. Другая задача — получение наименьшего среднего времени прохождения, т. е. времени между загрузкой задания пользователем и завершением его выполнения. С си- стемами разделения времени связана задача минимизации предполагаемого времени ответа (времени между нажатием клавиши ENTER на терминале и получением на это ответа си- стемы). Существует и много других задач планирования. Например, мы можем захотеть обеспечить гарантированный уровень об- служивания посредством ограничения максимально возможного времени прохождения и времени ответа. Или справедливости ради предоставить всем одинаковый уровень обслуживания. С другой стороны, исходя из внешних причин, возможно, потре- буется предоставить определенным заданиям наивысший при- оритет. В некоторых системах у пользователей имеется возмож- ность за большую цену получить более высокий приоритет; в этом случае целью планирования может быть заработать по- больше денег. Первые две задачи, упомянутые выше,— высокая пропускная способность и малое среднее время прохождения и время от- вета — обычно принимаются в качестве желательных свойств системы. К сожалению, они часто несовместимы. Рассмотрим, например, систему разделения времени, работающую с большим числом терминалов. Мы можем решить, что обеспечить лучшее время ответа нужно посредством более быстрого переключения управления между различными терминалами пользователей
368 Гл. 6. Операционные системы Для этого при диспетчеризации каждому процессу можно вы- делять меньший квант времени. Однако это приводит к увели- чению частоты операций контекстного переключения, что за- ставляет операционную систему чаще принимать решения по распределению ЦП и других ресурсов. Это означает, что на- кладные расходы операционной системы будут выше, а время, предоставляемое заданиям пользователей,— меньше, поэтому пропускная способность в целом уменьшится. Задание 1 — —---------— - Т, - 2 Т2 = 5 Задание 2 — — — — тср s 3»5 ________1 ..... I . I 1 I 1 2 3_4 5 Время (в минутах) а Задание 1- — — — — Т, = 4,5 Т2« 4,3 Задание 2 ----- —. —— ... тср - 4,4 J_______j________|_______L 2 3 4 5 Время (в минутах) Рис. 6.30. Сравнение времени прохождения и пропускной способности для а — однопрограммной системы, б — для мультипрограммной системы. С другой стороны, рассмотрим однопрограммную систему пакетной обработки. Каким образом в этой системе выполня- ются два задания, показано на рис. 6.30а. Обратите внимание на промежутки времени (они показаны пробелами на горизон- тальных линиях, соответствующих Заданиям 1 и 2), когда ЦП простаивал. Если оба задания запущены в момент времени 0, то время прохождения Задания 1 (Ti) равно 2 мин, а время прохождения Задания 2 (Т2) —5 мин. Среднее время прохож- дения ТСр равно 3,5 мин. Расмотрим теперь мультипрограммную систему, в которой одновременно выполняются два задания, как это показано на Ъис. 6.306. Заметьте, что разделение ЦП между заданиями происходит таким образом, что полное время простоя умень- шается; это феномен, похожий на тот, что показан на фис. 6.15., Из-за меньшего времени простоя два задания завер-
6.3. Машинно-независимые свойства операционных систем 369 шаются быстрее: за 4,5 мин вместо 5 мин. Это означает, что про- пускная способность системы стала больше: тот же самый объем работы был сделан за меньшее время. Однако среднее время прохождения задания стало хуже: 4,4 мин вместо 3,5 мин. Как правило, чаще всего используются две стратегии плани- рования заданий: первым пришел — первым обслужен (FCFS — First Соте — First Served)^ и кратчайшее задание — первым (SJF— Shortest Job First). Стратегия FCFS стремится обслуживать все задания одинаково, минимизируя тем самым время прохождения; SJF обеспечивает снижение среднего вре- мени прохождения, так как короткие задания могут вынужденно ожидать обслуживания в течение долгого времени. Примеры этих свойств и обсуждение других стратегий планирования мож- но найти в Дейтел [1984], Лорин [1981], Мэдник [1974]. 6.3.3. Распределение ресурсов В разд. 6. 2 обсуждалось, каким образом операционная си- стема может управлять такими ресурсами, как центральная память, каналы ввода-вывода и ЦП. Эти ресурсы используются всеми заданиями пользователей; распределение осуществляется системой автоматически. Ниже будет рассмотрена более общая функция распределения ресурсов, выполняемая многими опера- ционными системами. Ее можно использовать для управления распределением таких ресурсов пользователя, как файлы и структуры данных. На рис. 6.31а при помощи двух программ иллюстрируется необходимость применения этой функции. Обе программы используют последовательный стек, созданный ка- кой-то третьей программой. Внешние переменные STACK и ТОР содержат соответственно базовый адрес стека и относительный адрес элемента данных, находящегося в его вершине. Предпо- лагается, что внешние ссылки на переменные STACK и ТОР обрабатываются методами, аналогичными рассмотренным в гл. 3. Программа Р1 добавляет данные в стек; для этого она увеличивает предыдущее значение ТОР на 3, заносит содержи- мое регистра А в вершину стека, а затем запоминает новое значение ТОР (строки 24—27). Программа Р2 изымает дан- ные; для этого она заносит значение из вершины стека в ре- гистр А и уменьшает затем значение ТОР на 3 (строки 37— 40). Для простоты мы не использовали команду, требуемую для обработки пустого стека и переполнения. Если процессы Р1 и Р2 выполняются одновременно, то они мо- гут работать как правильно, так и неправильно. Предположим, например, что текущее значение ТОР равно 12. Если выполнятся команды с номерами 24—27 программы Р1, то в байты Такая стратегия обслуживания часто называется FIFO. — Прим, ред.
370 Гл. 6. Операционные системы 1 Р1 2 3 START 0 EXTREF STACK,TOR UDS 03 РЕГИСТР S « КОНСТАНТА 3 24 +LDX ТОР ВЗЯТЬ УКАЗАТЕЛЬ ВЕРШИНЫ СТЕКА 25 ADDR S.X УВЕЛИЧИТЬ ЕГО ЗНАЧЕНИЕ 26 +STA STACK,X ДОБАВИТЬ В СТЕК НОВОЕ ДАННОЕ 27 +STX ТОР СОХРАНИТЬ НОВОЕ ЗНАЧЕНИЕ УКАЗАТЕЛЯ 48 END 1 Р2 START 0 2 EXTREF STACK,TOP 3 IDS 03 РЕГИСТР S » КОНСТАНТА 3 37 +LDX ТОР ВЗЯТЬ УКАЗАТЕЛ» ВЕРНИНЫ СТЕКА 38 +LDA STACK,X ВЗЯТЬ НОВОЕ ДАННОЕ ИЗ СТЕКА 39 SUBR s,x УМЕНЬШИ» ЗНАЧЕНИЕ УКАЗАТЕЛЯ 40 +STX TOP СОХРАНИТЬ НОВОЕ ЗНАЧЕНИЕ УКАЗАТЕЛЯ END a Рис. 6.31. Управление ресурсами и использование сервисных запросов опе- рационной системе. 15—17 стека ею будет занесено новое данное, а значение ТОР станет равно 15. Если затем будут выполнены команды 37— 40 программы Р2, то только что добавленное Р1 данное будет считано, а значение ТОР снова станет равным 12. Так выгля- дит правильная работа Р1 и Р2; оба процесса выполняют тре- буемые операции над стеком, не влияя друг на друга. Пра- вильная последовательность работы будет и тогда, когда сна- чала Р2 выполнит строки 37—40, а затем Р1—строки 24—27. Теперь предположим, что после выполнения строки 24 у Р1 кончается отведенный ей квант времени. Происшедшее в резуль-
6.3. Машинно-независимые свойства операционных систем 371 1 Pt START 0 2 EXTREF STACK,TOP 3 LOS *3 РЕГИСТР 9 я КОНСТАНТА 3 LDT -SHAME УСТАНОВИТЬ УКАЗАТЕЛЬ НА ИМЯ FECJTCA 23 sue 3 ЗАПРОСИТЬ РЕСУРС 24 ОС TOP ВЗЯТЬ УКАЗАТЕЛЬ ВЕРШИНЫ СВА 25 ADDR s,x УВЕЛИЧИТЬ ЕГО ЗНАЧЕНИЕ 26 +STA- STACKS ДОШИТЬ В СТЁК НОВОЕ ДАННОЕ 27 +STX TOP СОХРАНИТЬ НОВОЕ ЗНАЧЕНИЕ УКАЗАТЕЛЯ 28 SVC 4 ОСВОБОДИТЬ РЕСУРС 47 SHAME 49 BYTE 'STACK1' ENB 1 P2 START 0 2 EXTREF STACK,TOP 3 EPS « РЕГИСТР S = КОНСТАНТА 3 35 LDT *STKHM УСТАНОВИТЬ УКАЗАТЕЛЬ НА ИМЯ РЕСУРСА 36 SUG 3 ЗАПРОСИТЬ РЕСУРС 37 4LDX TOP ВЗЯТЬ УКАЗАТЕЛЬ ВЁРЮШ СТЕКА 38 STACKS ВЗЯТЬ НОВОЕ ДАННОЕ ИЗ СТЕКА 37 SUBR s,x УМЕНЬШИТЬ ЗНАЧЕНИЕ УКАЗАТЕЛЯ 40 4STX TOP СОХРАНИТЬ НОВОЕ ЗНАЧЕНИЕ УКАЗАТЕЛЯ 41 sue 4 ОСВОЮДИТЬ РЕСУРС 74 STKNH BYTE 'STACK!' 73 EMU a. Рис. 6.31. Продолжение. тате этого прерывание от таймера вызовет сохранение значений всех регистров; сохраненное значение регистра X будет равно 12. Предположим теперь, что диспетчер передает управление Р2, которая затем выполняет строки 37—40. Так как значение ТОР. программой Р1 еще не было изменено, Р2 возьмет данное из байтов 12—14 стека и сделает значение ТОР равным 9. Когда Р1 снова получит управление ЦП, регистр X все еще будет
372 Гл. 6. Операционные системы содержать значение 12. Таким образом, в результате выполне- ния строк 25—27 в байты 15—17 стека будет добавлено новое данное, а значение ТОР установлено в 15. Последовательность только что рассмотренных событий при- ведет к неправильной работе Р1 и Р2. Данное, считанное Р2 по-прежнему как бы является частью стека, и получается, что стек содержит на одно данное больше, чем должно быть. Не- правильные результаты получаются и в некоторых других слу- чаях. Такие ситуации могут возникать всякий раз, когда два одновременно работающих процесса пытаются обменяться од- ним и тем же файлом или структурой данных. Избежать ситуаций такого рода можно, монополизировав управление стеком на время выполнения операций модифика- ции каждой из программ. На рис. 6.316 показано типичное ре- шение проблемы, при котором используются запросы на обслу- живание операционной системе. Выполнением команды SVC 3 программа Р1 запрашивает монопольное управление стеком. Требуемый ресурс задается регистром Т, указывающим на ло- гическое имя (определяемое пользователем), присвоенное стеку. После добавления в стек нового данного и изменения ТОР Р1 отказывается от монопольного управления, выполнив команду SVC 4. Программа Р2 осуществляет аналогичную последова- тельность действий. Операционная система в ответ на запрос управления ресур- сом проверяет, назначен ли он какому-нибудь другому процес- су или нет. Если ресурс свободен, то управление передается запрашивающему процессу. Если ресурс занят, то система пе- реводит запрашивающий процесс в заблокированное состоя- ние, в котором он находится до тех пор, пока ресурс не станет доступным. Пусть, например, в данный момент свободен ресурс STACK1. Если Р1 запросит его (строка 23), то управление операционной системой будет возвращено непосредственно этой программе. Как и раньше, предположим, что сразу после вы- полнения строки 24 заканчивается квант времени, выделенный Р1. Затем управление ЦП переходит к Р2; однако STACK1 остается занятым Р1. Таким образом, после запроса Р2 ресур- са STACK1 в строке 36 программа будет переведена в состоя- ние блокировки. В конце концов Р1 снова получит управление и завершит работу со стеком. Когда Р1 освободит STACK1 ;(строка 28), он будет передан Р2, которая из состояния блоки- ровки перейдет в состояние готовности и после получения уп- равления ЦП сможет выполнить операцию модификации. Чи- тателю следует внимательно просмотреть последовательность этих событий, чтобы увидеть, как с помощью данного метода удается избежать возникновения описанных выше проблем. К сожалению, использование операций запроса и отказа может привести к проблемам другого рода. Рассмотрим, напри-
6.3. Машинно-независимые свойства операционных систем 373 1 РЗ START 0 2 LDT «Rl ЗАПРОС RES1 3 SVC 3 * • 4 LDT =R2 ЗАПРОС RES2 5 SVC 3 6 LDT =R2 ОСВОБОЖДЕНИЕ RES2 7 SVC 4 • 8 LDT =R1 ОСВОБОЖДЕНИЕ RES1 9 SVC 4 10 R1 BYTE 'RES1' 11 R2 BYTE 'RES2' 12 END 1 Р4 START Ъ 2 LDT =R2 3 SVC 3 ЗАПРОС RES2 4 LDT «Rl ЗАПРОС RES1 5 SVC 3 6 LDT =R1 ОСВОБОЖДЕНИЕ RES1 7 SVC 4 LDT =R2 ОСВОБОЖДЕНИЕ RES2 SVC 4 9 10 Rl BYTE 'RES1' •11 R2 BYTE ZRES2' 12 END Рис. 6.32. Запрос ресурсов, приводящий к потенциальной самоблокировке. мер, программы на рис. 6.32. Первой управление ресурсом RES1 запрашивает РЗ; позже она запрашивает ресурс RES2. Программа Р4 использует те же два ресурса, однако RES2 она запрашивает перед RES1. Предположим, что РЗ после запроса получает управление ресурсом RES1; при этом отведенный ей квант времени конча- ется до того, как она сможет затребовать RES2. Затем может быть активизирована Р4. Предположим, что Р4 запрашивает и
374 Гл. 6. Операционные системы получает управление ресурсом RES2. Такая последовательность событий ведет к ситуации, в которой дальше не смогут выпол- няться ни РЗ, ни Р4. Действительно, программа Р4, дойдя до строки 4, затребует управление ресурсом RES1, после чего она будет переведена в состояние блокировки, так как RES1 отдан РЗ. Аналогично РЗ, дойдя до строки 4, затребует управление RES2 и тоже будет переведена в состояние блокировки, так как RES2 отдан Р4. Поскольку свободных ресурсов нет, ни один процесс не сможет продолжить работу. Такая ситуация является примером взаимоблокировки (de- adlock); она характеризуется тем, что в некотором наборе про- цессов каждый отдельный процесс постоянно заблокирован, так как необходимые ресурсы заняты другими процессами. Взаимо- блокировка снимается освобождением ресурса за счет удаления из решения одного или нескольких вовлеченных в нее заданий. Существует ряд методов, позволяющих избежать взаимоблоки- ровки. Например, система может потребовать, чтобы процесс запрашивал все свои ресурсы одновременно или делал это в определенном порядке (как то: RES1 перед RES2). К сожале- нию, при этом из-за продолжительной занятости ресурсов сни- зится работоспособность всей системы. Описание методов на- хождения и предотвращения взаимоблокировок можно найти в Дейтел [1984]. Выше рассмотрены частные случаи общих проблем взаимо- исключения и синхронизации процессов. Их обсуждение и спо- собы разрешения можно найти в Дейтел [1984] и Холт [1978]. 6.3.4. Защита Во всякой операционной системе, обслуживающей одного или нескольких пользователей, должны существовать средства, обес- печивающие защиту отдельного пользователя от возможных не- санкционированных действий других. Например, пользователь должен иметь возможность создавать файлы, которые не могут быть прочитаны или изменены другими пользователями. В це- лом проблема безопасности и защиты достаточно сложна. В данном разделе будут коротко рассмотрены основные функ- ции защиты, выполняемые типичной операционной системой. Они должны быть совмещены с безопасностью на физическом уровне, с административными процедурами и другими способа- ми контроля, осуществляемыми для обеспечения эффективной защиты. Дополнительную информацию по защите, безопасности и секретности можно найти в Дейтел [1984], Деннинг [1983}] и Фернандес [1981]. В большинстве многопользовательских операционных систем существует некоторый вид контроля доступа или авториза- ции^ чаще всего они реализуются на основе матрицы доступа,
6.3. Машинно-независимые свойства операционных систем 375 аналогичной той, что показана на рис. 6.33. В этом примере пользователи обозначены щ, и2 и и3. Права пользователя по отношению к некоторому объекту (файлу или программе) за- даются строками в матрице доступа. Так, пользователь и2 мо- жет читать файлы fi и f2, писать в файл f2, выполнять про- грамму р2. Однако ему запрещен какой-либо доступ к файлу f3 или программе Права на доступ к вновь созданному объекту обычно определяются его создателем. В некоторых си- стемах пользователям, обладающим определенными правами на Пользо* ватели Ut fl Файлы h fl Программы Pt Pa R R H E R RM E R.W R Rte Рис. 6.33. Пример матрицы доступа. доступ, разрешено передавать их другим пользователям. До- ступ к контролируемым объектам осуществляется при помощи запросов на обслуживание операционной системе. Каждому пользователю, авторизация которого произведена неверно, в доступе будет отказано. В действительности матрица доступа достаточно разрежена. Большинство пользователей имеет доступ к относительно ма- лому числу объектов системы. По этой причине информация, связанная с правами на доступ, часто хранится в виде списка авторизаций для каждого объекта (т. е. списка авторизованных пользователей) или списка возможностей (т. е. списка объек- тов, к которым возможен доступ). Дальнейшую информацию по реализации модели матрицы доступа можно найти в Деннинг [[1983]. Эффективность только что описанного метода зависит, оче- видно, от правильной идентификации пользователей. С тем чтобы применить верные правила контроля за доступом, операци- онная система должна иметь возможность распознавать каждое го пользователя. Один из наиболее часто используемых спосо- бов идентификации системные пароли. В большинстве систем пользователю для запуска задания необходимо ввести секрет- ный пароль или зарегистрироваться с помощью интерактивного терминала. Иногда этот способ применяется шире и подтверж- дение пароля требуется для доступа к конкретным файлам, программам и т. п. В некоторых системах имеется главная таблица паролей, используемая операционной системой для проверки паролей, получаемых от пользователей; однако она является потенциально уязвимым местом всей системы защиты. Во избежание этого во многих операционных системах таблица
376 Гл. 6. Операционные системы хранится в зашифрованном виде. Более подробно об этом и других подобных методах можно узнать в Деннинг [1983]. Так как информация иногда должна находиться вне средств защиты, система идентификации и авторизации пользователей не всегда позволяет разрешить все проблемы, связанные с без- опасностью. Рассмотрим, например, рис. 6.34а. Идентификация и авторизация пользователя за интерактивным терминалом про- изведены правильно. Мы предполагаем, что вычислительная си- стема осуществляет соответствующий контроль доступа и имеет средства обеспечения безопасности на физическом уровне. Рис. 6.34. Использование кодирования данных для их защиты во время пе* редачи. Предполагается также, что и сам терминал на физическом уровне защищен. Однако линию связи между вычислительной системой и терминалом защитить бывает сложно или вообще невозможно. (Примером может служить проблема предотвра- щения подслушивания, когда терминал связан с ЭВМ через обычную телефонную сеть.) Это означает, что во время переда- чи уязвимой является любая информация из файла F, которая передается терминалу. Аналогично можно узнать все пароли, посылаемые пользователем, а следовательно, получить доступ к защищенным объектам. Обычно проблемы такого рода решаются с помощью шиф- ровки данных. Информация, которая должна быть передана по незащищенной линии связи, зашифровывается (кодируется),ко- гда она еще находится в пределах средств защиты. Переданная информация расшифровывается (декодируется) уже под защи-
6.4. Способы построения операционных систем 377 той соответствующих средств получателя. Прослушивающий не сможет понять передаваемую зашифрованную информацию. Операции шифровки и дешифровки одинаково успешно могут выполняться как аппаратурой, так и программным обеспече- нием. Способов шифровки существует много. Для более под- робного ознакомления с этими методами смотрите Деннинг [1983]. Конечно, эффективность любой системы защиты всецело за- висит от правильности и защищенности ее самой. В рассмотрен- ной нами системе информация по контролю за доступом долж- на быть защищена от неавторизованных изменений. Должен существовать и механизм, обеспечивающий невозможность до- ступа пользователей к защищенным объектам, минуя систему безопасности. В этой связи часто бывают полезны такие аппа- ратные средства, как режимы пользователя/супервизора, прич вилегированные инструкции и механизм защиты памяти. Очень важно быть уверенным и в том, что часть операционной систем мы, связанная с правилами защиты {ядро безопасности) функ- ционирует правильно. Дальнейшее обсуждение этих вопросов может быть найдено в Дейтел [1984], где есть и обзор слабых мест в средствах обеспечения безопасности операционных си* стем, и интересные и поучительные случаи по вскрытию за- щиты системы. 6.4. Способы построения операционных систем В этом разделе будут вкратце рассмотрены некоторые важ-' ные вопросы, касающиеся создания операционных систем и их структуры. В разд. 6.4.1 вводится понятие операционной систе- мы с иерархической структурой, используемой при построении многих реальных систем. В разд. 6.4.2 показано, как операци- онная система может поддерживать многочисленные виртуалы* ные машины. С их помощью у пользователя создается впечат- ление, что он работает на выделенной ему части аппаратных средств. Разд. 6.4.3 посвящен мультипроцессорным операцион- ным системам; в нем обсуждаются некоторые способы распре- деления заданий между процессорами. 6.4.1. Иерархическая структура Многие реально существующие операционные системы раз- работаны и реализованы на основе иерархической структуры. Примеры подобной структуры приведены на рис. 6.35. В этом случае основным принципом, как видно из рис. 6.35а, является обобщение идеи расширенной машины, приведенной на рис. 6.1. Каждый слой, или уровень, в структуре может использовать
378 Гл. 6. Операционные системы Уровень Функции 3 Управление файлами 2 Управление памятью 1 Управление вводом-выводом О Диспетчеризация, управлений ресурсами 6 Рис. 6.35. Пример системы с иерархической структурой. функции, предоставляемые ему более низким уровнем, как если бы они являлись частью реальной машины. Так, Уровень 0, ча- сто называемый ядром (kernel) операционной системы, имеет дело непосредственно с аппаратурой; Уровень 1 имеет дело с интерфейсом, предоставляемым уровнем 0, и т. д. Программы пользователя имеют дело с интерфейсом самого высокого уров- ня (в данном случае Уровня 3), который представляет собой интерфейс пользователя, рассмотренный в разд. 6.1. На рис. 6.356 показаны функции, соответствующие уровням рассматриваемой структуры. Их расположение определяется взаимоотношениями между выполняемыми ими операциями. В общем случае функции каждого уровня могут обращаться лишь к функциям того же или более низкого уровня; таким образом, не должно быть внешних вызовов. В нашем примере программы управления файлами (Уровень 3) для распределения .буферов должны использовать менеджер памяти (Уровень 2), а для чтения и записи блоков — супервизор ввода-вывода. Если управление памятью производится при помощи размещения
6.4. Способы построения операционных систем 379 страниц по запросу, то менеджер памяти для передачи страниц между реальной памятью и вспомогательной тоже должен вы* зывать супервизор ввода-вывода. Все уровни системы исполь- зуют предоставляемые Уровнем 0 функции планирования про- цессов и управления ресурсами. У иерархической структуры много преимуществ. Системные программы на каждом уровне могут использовать относительно простые функции и интерфейсы, предоставляемые более низки- ми уровнями. Программисту нет необходимости вникать в то* как они в действительности реализуются. Операционная систе- ма может быть создана и отлажена по уровням, начиная с ну- левого. Это сильно снижает сложность каждой части системы и намного упрощает реализацию заданий и их отладку. На рис. 6.356 показано типичное расположение функций, од- нако различные системы существенно отличаются друг от дру- га. Рассмотрим, например, программы обработки прерываний^ Во многих системах обработчики прерываний первого уровня (FLIH— First-Level Interrupt Handlers) размещены в ядре (Уровень 0). После начальной обработки прерываний FLIH может передать управление программе более высокого уровня; это исключение из правил, согласно которым внешние вызовы недопустимы. Так, например, FLIH в случае прерывания по отсутствию страницы может сохранить информацию о состоя- нии, разрешить другие прерывания, а затем передать управле- ние программе Уровня 2 (см. рис. 6.25в). В некоторых операционных системах иерархическое распре- деление функций в особых случаях сделано более сложным. Информацию об этом и о других примерах иерархических структур можно найти в Лорин [1981] и Питерсон [1983]. Иерархические системы различаются также правилами пе- редачи управления с одного уровня на другой. В строгой иерархии каждый уровень может обращаться только к уровню, находящемуся непосредственно под ним. Так, Уровень 3 может связываться только с Уровнем 2. Если программе управления файлами в нашем примере необходимо вызвать супервизор вво- да-вывода, то этот запрос будет передан с Уровня 2 на Уро- вень 1. Преимущество этого подхода заключается в простоте использования: каждый уровень взаимодействует только с од- ним интерфейсом. Однако такое ограничение может привести к потере эффективности, так как возрастает число вызовов, ко- торые нужно сделать для достижения внутреннего уровня, В прозрачной иерархии (transparent hierarchy) каждый уро- вень может связываться непосредственно с интерфейсом любого более низкого уровня. Так, например, программа пользователя может обращаться к программам управления файлами Уров- ня 3 или непосредственно вызывать функции супервизора вво- да-вывода Уровня 1.
380 Гл. 6. Операционные системы Дальнейшее обсуждение иерархических структур операцион- ных систем можно найти в Питерсон [1983], Лорин [1981] и Мэдник [1974]. 6.4.2. Виртуальные машины Чтобы перед пользователями создать иллюзию того, что они работают на автономных виртуальных машинах, концепцию иерархической структуры, рассмотренную в предыдущем разде- ле, можно расширить. Подход, связанный с применением вир- туальных машин, делает возможной одновременную работу раз- личных операционных систем на одной и той же реальной ма- Олерационная система ОС! Операционная система ОС 2 Операционная система ОСЗ (тест) Пользователь? (автономная программа) Монитор виртуальной машины (МВМ) Реальная машина Рис. 6.36. Операционная система с виртуальными машинами и многими пользователями. шине. Таким образом, виртуальные машины мы можем рас- сматривать как перенесение принципов мультипрограммирова- ния на самый нижний уровень операционной системы. Рассмотрим, например, рис. 6.36. Операционная система ОС1 — мультипрограммная, поддерживающая одновременную работу трех пользователей; операционная система ОС2 — одно- программная; ОСЗ — операционная система, которая в этот мо- мент тестируется. Есть также программа пользователя (Поль- *зователь5), предназначенная для работы в режиме супервизора в качестве автономной программы, не находящейся под управ- лением операционной системы.
6.4. Способы построения операционных систем 881 Все три операционные системы плюс автономный пользова- тель фактически работают на одной реальной машине. Однако они имеют дело не с ней, а с монитором виртуальной машины (МВМ), благодаря которому у каждого пользователя создается впечатление, что он работает на автономной машине. Таким образом, в то время как обычные пользователи обслуживаются традиционным способом, имеется возможность отлаживать но- вые операционные системы, а также разрешить пользователям Рис. 6.37. Виртуальная машина как расширение понятия иерархической структуры. в особых случаях работать в режиме супервизора Если тес- тируемая система или автономный пользователь разрушит си- стему, то это отразится лишь на их виртуальных машинах. Другие пользователи реальной машины смогут спокойно про- должать свою работу. На рис. 6.37 показано, каким образом создается подобная иллюзия. Программы самого нижнего уровня операционной системы имеют дело не с реальной машиной, а с монитором Автор идеализирует положение дел. В режиме виртуальных машин можно отлаживать только те части операционных систем, которые не за- трагивают аппаратных управляющих регистров и других средств аппара- туры, используемых монитором ОС. — Прим. ред.
382 Гл. 6. Операционные системы виртуальной. МВМ, совершенно невидимый ни операционно! системе, ни программе пользователя, предоставляет те же ре- сурсы, обслуживание и функции, что и лежащая в основе ре- альная машина. Каждый непосредственный пользователь виртуальной маши- ны, например ОС1 или Пользовательб на рис. 6.36, фактически работает не в режиме супервизора, а в пользовательском ре- жиме. Когда такой пользователь пытается выполнить привилеги- рованную команду, такую как SIO, STI или LPS, происходит программное прерывание. По нему управление передается мо- нитору виртуальной машины. МВМ имитирует (с учетом вирту- альности машины) выполнение требуемой команды, а затем возвращает управление пользователю. Монитор виртуальной ма- шины активизируется и по прерыванию на реальной машине. Он определяет, какая из виртуальных машин должна быть за- действована, и производит соответствующие изменения ее со- стояния. Практически монитор виртуальной машины представляет со- бой завершенную, хотя и простую, операционную систему ре- альной машины. Другие операционные системы и автономные пользователи виртуальной машины являются «пользователями» реальной операционной системы (МВМ). Таким образом, мони- тор виртуальной машины призван осуществлять все самые су- щественные, рассмотренные нами машинно-зависимые функции. МВМ сохраняет для каждой машины информацию о состоянии и распределяет реальный ЦП между различными виртуальными машинами; это не что иное, как выполнение функции планиро- вания процессов, рассмотренной в разд. 6.2. Используя методы, аналогичные рассмотренным выше, МВМ выделяет каждой вир- туальной машине самостоятельную виртуальную память и не- сколько виртуальных каналов ввода-вывода. Наиболее очевидные преимущества виртуальных машин — это гибкость и удобство. Чтобы удовлетворить нужды различ- ных пользователей, одновременно могут работать разные опе- рационные системы. В то время как производится тестирование операционных систем или программ автономных пользователей, машина по-прежнему остается доступной и для обычных поль- зователей. При работе с виртуальными машинами можно до- стичь и более высокой степени защиты, поскольку ни одна вир- туальная машина не имеет доступа к ресурсам другой. Недо- статком, безусловно, является значительная сложность системы, моделирующей работу каждой виртуальной машины. Например, если операционная система, функционирующая на виртуальной машине, сама использует средства виртуальной памяти, то мо- жет понадобиться наличие двух раздельных уровней трансля- ции динамических адресов. Эффективность операционной систе- мы виртуальной машины в основном зависит от того, сколько
6.4. Способы построения операционных систем 383 операций должно моделироваться МВМ, а сколько может быть выполнено непосредственно на реальной машине. Дальнейшее обсуждение операционных систем виртуальных машин можно найти в Дейтел [1984] и Питерсон [1983]. 6.4.3. Мультипроцессорные системы До сих пор при обсуждении операционных систем мы имели дело с машинами, в состав которых входил один центральный процессор (ЦП). В этом разделе будут рассмотрены некоторые возможные пути проектирования мультипроцессорных операции онных систем. Рис. 6.38. Мультипроцессорные системы со слабо связанными процессорами. Операционная система мультипроцессорной машины должна выполнять функции, аналогичные тем, что мы рассматривали для однопроцессорных систем. Конечно, некоторые из них могут нуждаться в изменениях. Например, планировщик процессов мо- жет выделить пользовательским заданиям более одного ЦП, поэтому в активном состоянии может находиться сразу несколь- ко процессов. Существует также ряд менее очевидных вопросов, большинство из которых связано с организацией системы в це- лом. На рис. 6.38 показана простейшая форма мультипроцессор- ной организации. Каждый процессор имеет свою собственную память, устройства ввода-вывода и другие ресурсы. Вдобавок на каждом процессоре функционирует его собственная опера- ционная система, Процессоры соединяются линиями связи.
384 Гл. 6. Операционные системы Посылка по ним запроса является единственным способом полу- чения доступа одного процессора к ресурсам другого. Подобная организация, часто называемая мультипроцессорной системой со слабо связанными процессорами, очень похожа на сеть из однопроцессорных систем. Такие системы часто используются, если среди различных процессоров существует специализация функций. Например, некоторые системы разделения времени для осуществления всех деталей управления связью с термина- лами пользователей имеют периферийный процессор. Главный процессор выполняет всю текущую вычислительную работу, Рис. 6.39. Мультипроцессорные системы с сильно связанными процессорами С обработкой: а — типа главный — подчиненный и б — симметричной. связываясь с периферийным процессором всякий раз, когда требуется произвести обмен с терминалами. Операционная си- стема, работающая на каждом процессоре мультипроцессорной системы со слабо связанными процессорами, очень похожа на те, что мы рассматривали при обсуждении однопроцессорных систем. Единственной действительно новой функцией является управление сообщениями, посылаемыми по линиям связи. Мультипроцессорные системы со слабо связанными процес- сорами относительно просты, потому что каждый ресурс выде- ляется какому-нибудь одному процессору. С другой стороны, в некоторых мультипроцессорных системах допускается прямое распределение ресурсов между процессорами. Такая организа- ция, называемая мультипроцессорной системой с сильно связан- ными процессорами, отчасти усложняет задачи операционной системы. На рис. 6.39 демонстрируются две разновидности мульти- процессорной системы с сильно связанными процессорами. На рис. 6.39а показана система, реализующая принцип главный —- подчиненный. Это означает, что все управление ресурсами и другими функциями операционной системы осуществляется одним главным процессором. На него полностью возложено
6.4. Способы построения операционных систем 385 управление работой всех подчиненных процессоров, выполняю* щих задания пользователей. Процессоры взаимодействуют либо непосредственно через линии связи, либо через рабочие области совместно используемой памяти. В мультипроцессорных систе- мах, реализованных по принципу главный — подчиненный, про- цессоры могут совместно использовать такие ресурсы, как память и файлы данных. Однако программы и структуры дан- ных, составляющих саму операционную систему, не разделяют- ся; они используются только главным процессором. Таким об- разом, такой тип операционной системы также несколько похож на рассмотренные нами однопроцессорные системы. Наиболее важной проблемой в мультипроцессорных систе- мах типа главный — подчиненный является несбалансирован- ность использования ресурсов. Например, главный процессор может быть перегружен запросами от служб операционной си- стемы, и это станет причиной длительных простоев подчинен- ных процессоров. Вдобавок любая неисправность в аппаратуре главного процессора вызывает останов всей системы. Всего это- го можно избежать, разрешив любому процессору выполнять любую функцию, которая будет затребована операционной си- стемой или программой пользователя. Этот подход, называемый симметричной обработкой (иногда используется термин «распре- деленная ОС».— Ред.), демонстрируется на рис. 6.396. С его помощью ликвидируются потенциально слабые места в системе типа главный — подчиненный, так как все процессоры имеют возможность выполнять один и тот же набор функций. Вдо- бавок неисправность одного процессора не обязательно вызо- вет выход из строя всей системы. Остальные процессоры могут продолжить выполнение всех необходимых функций. В симметричных мультипроцессорных системах различные части операционной системы могут выполняться одновременно разными процессорами. Из-за этого разработка подобных си- стем может оказаться значительно сложнее и труднее по срав- нению с рассмотренными выше. Например, все процессоры дол- жны иметь доступ ко всем структурам данных *>, используемым операционной системой. Однако процессоры имеют дело с этими структурами данных независима друг от друга, и, если два процессора одновременно пытаются изменить одну и ту же структуру, могут возникнуть осложнения (см. разд. 6.3.3). Сим- метричные мультипроцессорные системы должны иметь сред- ства, с помощью которых осуществляется управление доступом к структурам данных и к критическим таблицам операционной системы. Для решения этой задачи не достаточно операций за- проса и отказа, рассмотренных в разд. 6.3.3, так как два Под структурами данных здесь имеются в виду файлы, массивы и т. п. — Прим. ред. 13 Зак. 792
386 Гл. 6. Операционные системы различных Процессора могут выполнить операции запроса одно- временно. Поэтому обычно требуется, чтобы аппаратура обла- дала особыми свойствами, позволяющими одному процессору захватывать управление критическим ресурсом, блокируя на один шаг все другие процессоры. Обсуждение подобных сред- ств можно найти в Питерсон [1983} и Лорин [1981}. Дальнейшую информацию по операционным системам для мультипроцессоров можно найти в Дейтел [1984}, Лорин [1981} и Мэдник [1974]. 6.5. Примеры реализаций В этом разделе дается описание нескольких реально сущест- вующих операционных систем, специально выбранных для того, чтобы продемонстрировать разнообразие в разработке и приме- нении программного обеспечения такого рода. Как и в преды- дущих примерах, в наши задачи не входит дать полное и по- дробное описание каждой системы. Вместо этого мы выделим некоторые присущие им наиболее интересные или часто встре- чающиеся свойства, а для читателей, желающих получить боль- шую информацию, дадим ссылки на соответствующую литера- туру Первые два примера — операционные системы, не привязан- ные к какой-нибудь одной машине. В разд. 6.5.1 рассматрива- ется небольшая переносимая однопользовательская система UCSD Pascal, функционирующая на мини-ЭВМ. В разд. 6.5.2 описывается UNIX, более сложная операционная система, кото- рая также может быть реализована и на разных ЭВМ. Другие примеры — системы, разработанные для определен- ных семейств ЭВМ. Разд. 6.5.3 посвящен NOS™, функционирую- щей на ЭВМ серии CDC CYBER; это пример мультипроцессор- ной операционной системы. В разд. 6.5.4 рассматривается систе- ма VAX/VMS™, в которой используются некоторые интерес- ные методы управления виртуальной памятью. В разд. 6.5.5 рассматривается VM/370 фирмы IBM, операционная система, на базе которой могут быть реализованы многочисленные вир- туальные машины. 6.5.1, Система UCSD Pascal Система UCSD Pascal — это операционная система для ми- ни-ЭВМ. Написана она почти целиком на языке Паскаль и функционирует на псевдо-машине, или P-машине (разд. 5.4.3 и 5.5.2). Таким образом, она может работать на любой ЭВМ, где реализован соответствующий интерпретатор. Сама по себе система UCSD Pascal относительно проста. Поскольку она
6.5. Примеры реализаций 387 предназначена для поддержания работы одного пользователя на мини-ЭВМ, для ее реализации не требуется применения сложных средств. Одной из наиболее интересных частей системы UCSD Pas- cal является ее интерфейс с пользователями, имеющий древо- видную структуру. На каждом уровне дерева системой выда- ется строка-подсказка {prompt line), часто называемая меню i(menu). Она содержит список команд, которые может ввести пользователь. По каждой из них выбирается соответствующая ветвь, и пользователь переводится на более низкий уровень де- рева. Затем выдается новая строка-подсказка, содержащая команды, которые можно ввести уже на этом уровне. На самом нижнем уровне система запрашивает у пользователя при необ- ходимости дополнительную информацию и выполняет команду. Например, когда система загружена впервые, ею выдается строка-подсказка вида Команды: E(dit,R(un,F(ile,C(omp,L(ink,X(ecute,A(ssem,D(ebug. По команде Е пользователь переводится на один уровень ниже и попадает в Редактор (Editor), где выдается новая строка-подсказка. Она может иметь вид Редактор: A(djst,C(py,D(lete,F(ind,I(nsrt,J(mp,R(eplace, Q(uit,X(chng,Z(ap. (Реальные строки-подсказки в зависимости от версий системы могут немного различаться.) По команде I система запрашива- ет у пользователя вводимый текст. По команде Q пользователь выходит из Редактора и возвращается к корню дерева (на- чальной строке-подсказке.) На самом внешнем уровне имеющиеся команды обеспечива- ют простой доступ к наиболее важным функциям системы. Редактор позволяет пользователю ввести текст и отредактиро* вать любой заданный текстовой или системный рабочий файл.* IB программе Файлер имеются команды для управления файла- ми на диске. Это, например, команды выдачи справочной ин- формации, копирования и создания новых файлов. Компиля- тор, ассемблер и Редактор связей — это системные программы, выполняющие функции, аналогичные тем, что были рассмотрены ранее. По команде R(un внешнего уровня система начинает вы- полнение программы из текущего рабочего файла. По X(ecute система запрашивает имя файла и выполняет содержащуюся в нем программу. По D(ebug программа из текущего файла вы- полняется под управлением системного Отладчика, который вызывается в случае появления ошибки или по достижении ^контрольной точки, заданной пользователем. 13*
383 Гл. 6. Операционные системы В системе имеется также ряд вспомогательных программ. К ним относятся, например, такие, посредством которых произ- водится настройка системы на конкретную аппаратную конфи- гурацию, создаются самозагружающиеся программы, выполня- ются операции форматирования диска, не предусмотренные в Файлере. Вспомогательные программы запускаются на внеш- нем уровне при помощи команды X(ecute. Операционной си- стемой UCSD Pascal программы пользователей обеспечиваются набором лишь основных сервисных функций. В соответствии с нуждами программ, написанных на Паскале, в системе реали- зовано динамическое распределение памяти; тем самым имеется возможность в случае надобности переназначить содержимое основной памяти. Поддержка параллельности работы мини- мальна. Большая часть активности системы, включая операции ввода-вывода, синхронизована. Это означает, например, что опе- рация READ приводит к тому, что программе пользователя приходится ждать завершения физической операции ввода-вы- вода; в это время ни одно другое задание не может быть вы- полнено. Дальнейшую информацию по системе UCSD Pascal можно найти в SofTech Microsystems [1980 и 1983]. 6.5.2. UNIX0 Операционная система UNIX первоначально была разрабо- тана в Bell Laboratories для семейства ЭВМ DEC PDP-11. С тех пор она была реализована на многих других машинах, от больших ЭВМ до микропроцессоров. Сейчас существует не- сколько версий UNIX. Ниже будут рассмотрены некоторые при- сущие всем им наиболее интересные свойства системы. Интерфейс пользователя в системе UNIX реализован про- граммой, называемой оболочкой (shell). Это интерпретатор командных строк, воспринимающий строки, вводимые пользова- телем, в качестве запросов на выполнение других программ. Сначала по каждой команде вызывается соответствующая про- грамма. По ее окончании оболочка также завершает работу и ожидает от пользователя следующую команду. Или же обо- лочка может запросить команду сразу после запуска требуемой программы, не дожидаясь ее завершения. В функции оболочки входят также замена параметров, выполнение команд, основан- ных на сравнении символьных строк, а также передача управ- ления в пределах последовательности команд. Стандартная оболочка, рассмотренная выше, предназначена для того, чтобы предоставить пользователю доступ ко всем воз- По работе D. М. Ritchie and К. Thompson, «The UNIX Time-Sharing System». Communications of the ACM, Vol. 17, No. 7. Copyright 1974, As- sociation for Computing Machinery, Inc.
6.5. Примеры реализаций 33) можностям системы UNIX. Иногда бывает желательно преду* смотреть особый интерфейс пользователя, что можно легко до- стичь, применив нестандартный вариант оболочки. Имя реали- зующей ее программы может содержаться в пользовательском разделе файла паролей. Эта программа может интерпретиро- вать команды таким образом, чтобы обеспечивался интерфейс, приспособленный к требованиям особого пользователя. Напри- мер, для пользователей системы секретарского редактирования при помощи файла паролей может быть задано, чтобы вместо стандартной оболочки была использована программа редактиро- вания текстов. Таким образом, пользователи могут начать ра- боту сразу же после входа в систему. Оболочка может помочь и в обеспечении защиты системы: пользователям индивидуаль- ной командной оболочки может быть запрещено вызывать но предусмотренные для них программы системы UNIX. В системе UNIX процессам разрешено создавать другие про- цессы, независимо претендующие на системные ресурсы. Сис- темный вызов fork приводит к разбиению процесса на два выполняющихся независимо: родительский процесс и порожден- ный процесс. В UNIX имеются средства, обеспечивающие син- хронизацию процессов, при помощи которых родительский про- цесс может ожидать завершения работы порожденного процес- са, а также получать информацию о его состоянии. Процессы в UNIX могут связываться непосредственно друг с другом через логические межпроцессные каналы, называемые транспортерами (pipes). Для передачи сообщений между про- цессами используются обыкновенные запросы на чтение и запись. Ни процессу, ни тем более простому файлу не требует- ся знать, что в этом принимают участие транспортеры. Синхро- низация процессов и буферизация сообщений автоматически обрабатываются системой. Несколько процессов при помощи Транспортеров могут связываться в линейную структуру и обра- зовывать конвейер (pipeline), в котором выход одного процесса является входом другого. UNIX поддерживает иерархическую файловую систему. Для файлов пользователя имеются каталоги с древовидной структу- рой; система тоже имеет несколько каталогов для своих нужд. Рдин и тот же файл может фигурировать в нескольких различ- ных каталогах, возможно, и под разными именами. Все такие .файлы имеют одинаковый статус. Иными словами, файл не принадлежит какому-то одному определенному каталогу. Фай- лы могут быть и не включены ни в один каталог. Самое необычное свойство файловой системы UNIX — су- ществование специальных файлов. Каждому устройству ввода- рывода, поддерживаемому UNIX, соответствует по крайней мере один такой файл. Специальные файлы читаются и пишут- ся так же, как и обычные, содержащиеся на диске* Однако
390 Гл. 6. Операционные системы запрос на чтение и запись специальных файлов вызывает запуск соответствующего устройства. Это делает файлы ввода-вывода и устройства ввода-вывода очень похожими. Например, устрой- ства являются объектом того же самого механизма защиты, что и обычные файлы. Дальнейшую информацию и ссылки, касающиеся системы UNIX, можно найти в Баурн [1983], Дейтел [1984] и Ритчи [1978]. 6.5.3. NOS NOS — операционная система, предназначенная для исполь- зования на ЭВМ серии CDC CYBER и 6000. Это мультипро- цессорные машины, имеющие один или два центральных про- цессора (ЦП) и до 20 периферийных процессоров (ПП). ЦП и ПП работают с общей центральной памятью; таким образом, NOS является примером мультипроцессорной системы с сильно связанными процессорами. Однако к каналам и устройствам ввода-вывода ЦП не имеет прямого доступа. Этот доступ обес- печивается периферийными процессорами. Используя специаль- ные аппаратные команды, ПП могут запускать и останавли- вать ЦП. Под управлением NOS ЦП используется практически только для выполнения программ пользователей. ПП осуществляют ввод-вывод и большую часть функций операционной системы; однако определенная системная работа, если это может быть сделано с большей эффективностью, выполняется на ЦП. Каж- дому ПП в центральной памяти отведен блок из восьми слов, через который осуществляется связь с системой. Все управление машиной возложено на системный монитор, состоящий из программ MTR на ПП и CPUMTR на ЦП. MTR непрерывно сканирует запросы на обслуживание заданий поль- зователей. Подробно об этом будет рассказано ниже. CPUMTR назначает периферийным процессорам задания, по одному на каждого. Когда задание завершается, ПП оповещает систему. Этого ждет CPUMTR, прежде чем назначить ПП следующее задание. Каждому заданию пользователя в центральной памяти от- ведена определенная область. Машины, на которых функцио- нирует NOS, имеют аппаратно реализованные регистры пере- мещений, аналогичные тем, что были рассмотрены в разд. 6.2.4; поэтому существует возможность перемещать задания во время их выполнения. NOS получает от этого двойное преимущество. Во-первых, заданиям разрешается через запросы на обслужи- вание операционной системе увеличивать или уменьшать коли- чество выделенной им центральной памяти. Чтобы удовлетво- рить эти запросы, NOS перераспределяет содержимое централь-
6.5. Примеры реализаций 391 ной памяти. Во-вторых, NOS может временно приостановить выполнение некоторого задания с тем, чтобы сделать централь* ную память доступной процессу с более высоким приоритетом. Это называется свертыванием задания. Когда же задание вновь будет развернуто и продолжит выполнение, то не обязательно, чтобы оно размещалось в той же самой области центральной памяти, что и раньше. Первые 64 слова области памяти, выделенной заданию, за- резервированы под связь с операционной системой. Чтобы осу- ществить запрос на обслуживание операционной системе, про- грамма пользователя заносит его код в фиксированное место области связи. Программа MTR периферийного процессора не- прерывно сканирует эти области, проверяя наличие кода запро- са. Когда он обнаружен, MTR активизирует CPUMTR для того, чтобы тот назначил ПП для запрашиваемого действия. Средства, используемые NOS для синхронизации выполнения программ и сервисных функций, несколько отличны от тех, что были рассмотрены в разд. 6.2.2 и 6.2.3. Программа пользова- теля, запрашивающая услуги операционной системы, может по- требовать, чтобы ее временно перевели в состояние блокировки. Когда сервисная функция завершится, программа снова акти- визируется. В NOS это называется автоматическим перевызо- вом. Такое свойство часто используется, например, при ожида- нии завершения затребованной операции ввода-вывода. Если программа желает продолжать работу во время выпол- нения сервисной функции, то она запрашивает функцию без за- каза автоматического перевызова. Если программа должна ждать завершения затребованного сервиса, то она делает спе- циальный запрос на периодический перевызов, в результате чего она на некоторое время переводится в заблокированное состояние. По истечении этого времени программа снова акти- визируется и проверяет, может ли она продолжить работу или нет. Если сервисная функция еще не завершена, то программа вновь делает запрос на периодический перевызов. Аналогичный технический прием может быть использован для осуществления нескольких запросов на обслуживание, например одновремен- ного выполнения несвязанных друг с другом операций ввода- вывода. Дополнительную информацию по NOS можно найти в CDG [[1981 и 1983]. 6.5.4. VAX/VMS VAX/VMS — это многоцелевая операционная система, пред- назначенная для использования на ЭВМ серии VAX. Под ее управлением можно работать в трех режимах: пакетном, раз- деления времени и реального времени. Неотъемлемой частью
392 Гл. в Опороченные системы VAX является виртуальная память, а управление ею — одним из наиболее интересных свойств операционной системы VAX/VMS. Вся виртуальная память VAX/VMS состоит из 232 байт и делится на адресное пространство системы и адресное про- странство процессов размером 231 байт каждый. Адресное про- странство системы распределяется между всеми процессами, однако каждый процесс пользователя имеет и свое собственное адресное пространство. Оно в свою очередь делится на про- граммную область и область управления, В первой из них на- ходится программа, выполняемая процессом в текущий момент. Вторая отведена под системные стеки и прочую информацию, используемую операционной системой. Виртуальная память состоит из 512-байтовых страниц. Су- ществует системная таблица страниц, которая содержит по одному входу для каждой страницы адресного пространства системы. По нему можно судить, загружен ли соответствующий ей страничный кадр или нет. Начальный адрес и длина табли- цы задаются в двух специальных регистрах. Адресное простран- ство процессов описывается двумя таблицами страниц: одной для программной области, другой для области управления. Таблицы располагаются в адресном пространстве системы и са- ми могут участвовать в страничных операциях. Программы уп* равления памятью также используют таблицу, содержащую ин- формацию о состоянии всех страничных кадров в памяти, всех виртуальных страниц в системе и об их размещении. По страничному прерыванию активизируется системная про* грамма, называемая pager. Она выбирает подходящий странич- ный кадр и переписывает требуемую виртуальную страницу в память. Для каждого процесса число страниц, одновременна находящихся в памяти, ограничено. Когда достигается их пре* дельное число, pager выбирает страницу для выталкивания из памяти. Максимальное число страниц для каждого процесса устанавливается динамически и зависит от частоты происходя- щие в йем страничных прерываний. Измененные страницы, выталкиваемые из памяти, не пере* носятся во внешнюю память сразу. Вместо этого они заносятся в так называемый список измененных страниц. Чтобы миними* зировать накладные расходы, связанные со страничным обма- ном, pager пытается записывать страницы кластерами (т. е. по несколько страниц за одну операцию записи). Если страница из этого списка запрашивается до того, как она в действительности записана, то для ее восстановления не требуется выполнения какого-либо физического ввода-вывода, Аналогично неизмененные страницы, выбранные для откачки^ заносятся в список свободных страниц. Если входящая в него страница пртребуется до того, как страничный кадр был заново
6.5. Примеры реализаций 393 использован, она может быть перевызвана без выполнения фи- зического ввода-вывода. Другая программа операционной системы, называемая swap- per, используется для перемещения в память и из нее рабочих множеств целых процессов. Задача swapper — держать в памя- ти активные процессы с наивысшим приоритетом, чтобы они могли участвовать в планировании. Таким образом, функции swapper аналогичны тем, что выполняет планировщик проме- жуточного уровня, упоминавшийся в разд. 6.3.2. Функция планирования процессов в VAX/VMS основана на 32зуровневой системе приоритетов. Нижние 16 уровней зарезер- вированы для обычных процессов; остальные — для процессов реального времени. Каждый процесс в системе определен бло- ком управления процесса и своим заголовком. Блок управле- ния процесса содержит кластеры флагов событий, которые мо- гут быть использованы процессами для связи и координации их действий. Для синхронизации взаимодействующих процессов при ис- пользовании ими разделяемых ресурсов в VAX/VMS имеются средства блокировки. Существует шесть различных режимов блокировки, каждый из которых обеспечивает свой уровень разделения. Поддержка операций ввода-вывода осуществляется двумя уровнями: службой организации ведения записей (СОВЗ) и си- стемным сервисом ввода-вывода. Процедуры СОВЗ реализуют независимый от устройств файлово-структурированный доступ к устройствам ввода-вывода. Такие функции, как блокирование записей, могут быть выполнены либо СОВЗ, либо пользовате- лем. Обслуживание, зависящее и не зависящее от устройств, обеспечивается системным сервисом ввода-вывода. Пользовате- ли с достаточными привилегиями для задания своих файловых структур могут выполнять прямые операции ввода-вывода на персональных дисковых или ленточных устройствах. Оба типа поддержки ввода-вывода для создания очередей и выполнения физических операций ввода-вывода используют одни и те же системные программы более низкого уровня. Дальнейшую информацию по VAX/VMS можно найти в Дей- тел [1984] и DEC [1982]. 6.5.5. VM/370 ° VM/370 — операционная система для IBM 370, поддер- живающая работу многих виртуальных машин. Каждому ° Из «VM/370 — A Study of Multiplicity and Usefulness», by L. H. Sea* wright and R. A. MacKinnon, IBM Systems Journal, Vol, 18, No. 1, copyright 1979 International Business Machines Corporation.
394 Гл. 6. Операционные системы пользователю кажется, что он имеет дело с самой System/370, включая и каналы ввода-вывода. Система VM/370 состоит из двух основных компонентов: управляющей программы (УП) и диалоговой мониторной систе- мы (ДМС). УП является распорядителем ресурсов системы и выполняет те же функции, что и мониторы виртуальных ма- шин, рассмотренные в разд. 6.4.2. В УП также имеются коман- ды, помогающие в управлении и отладке операционной системы на виртуальной машине. Они позволяют выполнять такие дей- ствия, как чтение содержимого виртуальной памяти и установка контрольных точек. В сущности, это те же отладочные функции, которые системный программист может выполнить на пульте реального ЦП. На виртуальных машинах под управлением УП могут функ- ционировать все операционные системы, обычно используемые на ЭВМ серии 370. Включая, конечно, и саму УП. Возможность выполнения УП на виртуальной машине под управлением дру- гой УП делает возможной поддержку виртуального окружения VM/370. Это особенно полезно при отладке частей УП или постановке новых версий УП в системе. Диалоговый монитор системы (ДМС) обеспечивает интер- активную поддержку единственного пользователя на одном тер- минале. Работа со многими терминалами осуществляется бла- годаря тому, что УП имеют возможность поддерживать функ- ционирование многих виртуальных машин с ДМС на каждой из них. ДМС разработан специально для VM/370 и в своей работе прямо зависит от УП. В отличии от других операцион- ных систем для IBM 370 функционировать самостоятельно на реальной машине он не может. Однако под управлением УП ДМС работает эффективнее, чем другие системы. Можно сказать, что ДМС предоставляет своим пользовате- лям дружественный интерфейс. Он состоит скорее из набора команд, ориентированных на пользователя, чем из набора опе- раторов для управления заданиями, как это имеет место в дру- гих операционных системах серии 370. О самой операционной системе помимо того, что необходимо для выполнения требуе- мой функции, пользователю нужно знать сравнительно мало. Когда имеешь дело с использованием операционных систем, поддерживающих работу виртуальных машин, приходится при- нимать во внимание производительность всей системы. Издерж- ки, возникающие в результате функционирования монитора виртуальной машины, обычно уменьшают пропускную способ- ность системы и увеличивают время ответа. В VM/370 имеют- ся средства измерения производительности, с помощью которых во время выполнения УП можно получить информацию об ис- пользовании ресурсов, а также осуществить сбор результатов измерений для дальнейшего анализа.
Упражнения 39S Существует также несколько способов увеличения произво- дительности системы. Например, заданные страницы могут быть заблокированы в реальной памяти, а устройства и кана- лы— закреплены за определенной виртуальной машиной. Ка- кая-либо виртуальная машина может функционировать в режи- ме виртуальный-как-реальный. В этом случае память виртуаль- ной машины не участвует в осуществляемом под управлением УП размещении страниц по запросу. Другие усовершенствования в программном обеспечении бо- лее специфичны и подразумевают изменения в операционных системах, на которых реализовано виртуальное окружение. Эти изменения позволяют операционной системе виртуальной ма- шины узнать, что она работает под управлением VM/370, и связаться непосредственно с УП. Тем самым система не долж- на больше выполнять операции, которые при ее работе на вир- туальной машине были бы излишни. Эти изменения, называе- мые в совокупности рукопожатием, повышают эффективность и увеличивают производительность виртуальной машины. В определенных моделях System/370 и соответствующих процессорах имеются специальные средства, предназначенные для использования с VM/370. Они позволяют аппаратно управ- лять некоторыми наиболее часто выполняемыми УП функция- ми. В результате достигается значительное увеличение произво- дительности виртуальной машины. Дальнейшую информацию по VM/370 и соответствующие ссылки можно найти в Дейтел [1984] и Сирайт [1979]. Упражнения Раздел 6.2 1. В разд. 6.2.1 мы предполагали, что если происходит какое-то пре- рывание, то все другие прерывания равного или более низкого приоритетов запрещаются. Будет ли схема, рассмотренная в тексте, работоспособной, если мы просто запретим все прерывания того же класса? 2. Предположим, что обработка прерываний определенного класса не- обычайно сложна. Может оказаться нежелательным в течение всего вре- мени обработки оставлять другие прерывания закрытыми. Предложите метод обслуживания прерываний, при котором большую часть времени прерыва- ния могут оставаться открытыми. 3. В чем преимущества существования нескольких классов прерываний по сравнению с одним, при котором индикация класса прерывания осу- ществляется при помощи флагового бита? 4. Предположим, что время работы задания пользователя на ЦП ограничено. Его предельная величина для разных заданий различна. Какая часть операционной системы должна следить за тем, чтобы лимит времени не был превышен, и как это может быть сделано? 5. На УУМ/ДС установкой бита IDLE в 1 ЦП переводится в состояние простоя. Нужно ли, чтобы подобная возможность была реализована на ЭВМ, поддерживающей мультипрограммную работу?
396 Гл. 6. Операционные системы 6. Предположим, вы хотите создать мультипрограммную операционную систему, поддерживающую пакетный режим работы на ЭВМ, не имеющей интервального таймера. Какие возникнут проблемы? Можете ли вы приду- мать способ их решения с использованием каких-нибудь других средств, реализованных как аппаратно, так и программно? 7. Как вы ответите на вопросы упр. 6, если операционная система под- держивает еще и работу в режиме реального времени? 8. Рассмотрим мультипрограммную операционную систему. Предполо- жим, что в течение какого-то времени только одно задание пользователя находится в состоянии готовности и может выполняться на ЦП. По исте- чении отводимого ему кванта времени оно будет периодически прерываться., При этом диспетчер только что сохраненное состояние задания будет сразу же восстанавливать, а само задание активизировать, предоставляя ему еще один квант времени. Как можно избежать ненужных издержек такого рода, если считать, что операционная система по-прежнему может обслужи- вать и другие задания, как только они приходят в состояние готовности? 9. Вместо одного списка всех заданий с информацией об их состоянии в некоторых системах существует несколько списков (т. е. список готовых Заданий, список блокированных заданий и т. д.). Каковы преимущества и недостатки такого подхода? 10. В примере на рис. 6.15 считалось, что прерывания по таймеру от- сутствуют. Предположим, что квант времени, назначенный процессу Р1, Истекает в интервале между моментами (2) и (3). Перерисуйте диаграмму До момента (10) так, чтобы были отображены все возможные последова- тельности событий, которые могут произойти после прерывания по таймеру* 11. Перерисуйте диаграмму на рис. 6.15 с учетом того, что процесс Р2 имеет более высокий приоритет диспетчеризации, чем процесс Р1, и что используется приоритетное планирование процессов. 12. Предположим, что в мультипрограммном режиме выполняются два задания. Задание А много времени расходует на выполнение на ЦП и Мало — на ввод-вывод. Задание В занимает мало времени ЦП, а операций рвода-вывода осуществляет много. Какому из этих заданий следует при- своить больший приоритет диспетчеризации для увеличения производитель- ности всей системы? 13. Предположим, что супервизор ввода-вывода имеет возможность вы- бирать запросы из очереди к каналу, основанной на системе приоритетов. «Дакому из двух заданий упр. 12 следует дать в этом случае больший риоритет на ввод-вывод? 14. Предположим, что к некоторому устройству ввода-вывода можно получить доступ через любой из двух каналов ввода-вывода, однако каждый раз оно может быть использовано только одним каналом. Как с учетом этого нужно изменить рассмотренные нами программы супервизора ввода- вывода? 15. Какие вы выберете число и размер разделов для системы управле- ния памятью с разделами фиксированного размера? 16. Какие преимущества и недостатки имеет подход к защите памяти р использованием граничных регистров по сравнению с защитой по ключам? 17. Часто в мультипрограммной системе операция ввода-вывода для одного задания выполняется в то время, пока другое работает н£ ЦП« Как обеспечить защиту памяти для такой операции Ввода-вывода? Как, напри- $е6, можно предотвратить чтение данных одним заданием из раздела дан- ных другого?
Упражнения 397 18. Предположим, что в некоторой машине имеются флаговые биты, при помощи которых отражается тип каждого данного, сохраняемого в памяти или регистре (например, целый, символьный, число с плавающей точкой, команда, адрес). Как эта информация может быть использована для реализации в машине перемещаемых разделов? 19. Нужны ли аппаратные средства защиты памяти на машине, где управление памятью осуществляется при помощи размещения страниц по запросу? 20. Нужен ли на машине из упр. 19 перемещаемый загрузчик? 21. Некоторые системы с размещением страниц по запросу осуществ- ляют выбор страниц для выталкивания из памяти, исходя из того, была модифицирована страница или нет. То есть система предпочитает вытолк- нуть ту страницу, которая с момента загрузки не была модифицирована* Какие преимущества и недостатки у этого подхода? 22. Какие методы может использовать программист для увеличения ло- кальности ссылок в программе? Какие технические приемы программиро- вания и какие структуры данных могут привести к ухудшению локальности ссылок? 23. Как будет выглядеть диаграмма на рис. 6.266 для программы, у, которой отсутствует локальность ссылок (т. е. программа, в которой каждое обращение к памяти не зависит от предыдущего)? Как будет выглядеть диаграмма для программы с идеальной локальностью ссылок (т. е. та, у которой каждое обращение осуществляется к той же странице, что и предыдущее) ? 24. В мультипрограммной системе в то время как одно задание ожи- дает завершения операции ввода-вывода, другие могут выполнять полезную работу. Почему тогда пробуксовка одной программы в системе с виртуаль- ной памятью создает проблемы и для других программ? 25. Придумайте алгоритмы работы обработчиков прерываний четыре» уровней в мультипрограммной операционной системе с размещением страниц по запросу, работающей на УУМ/ДС. 26. Почему у прерываний по таймеру более низкий приоритет нц УУМ/ДС, чем у SVC-прерываний (см. разд. 6.2.1)? 27. Почему прерывание по вводу-выводу имеют более низкий приоритет на УУМ/ДС, чем SVC-прерывания? Раздел 6.3 1. Придумайте алгоритм работы программы управления файлами, ко- торая выполняет операции блокирования и буферизации, демонстрируемые на рис. 6.28. 2. Когда может быть выгодно при работе с последовательным файлом использовать более двух буферов? 3. Нарисуйте диаграмму изменения состояний, аналогичную приведен- ной на рис. 6.7, для трехуровневой процедуры планирования, показанной на рис. 6.296. 4. Может ли быть, чтобы время прохождения задания в мультипро- граммной операционной системе было меньше времени его прохождения в однопрограммной системе на той же машине? 5. Опишите алгоритмы работы и структуры данных программ опера- ционной системы, реализующие функции запроса и отказа, рассмотренные в разд, 6,3,3м
’398 Гл. 6. Операционные системы 6. Предположим, что в системе блок состояния событий для каждого ресурса определен таким образом, что «событие наступило» эквивалентно в логическом смысле «ресурс свободен». Могут ли функции запроса и от- каза быть реализованы с использованием операций WAIT и SIGNAL, рас- смотренных в разд. 6.2.2? 7. Рассмотрим две программы, приведенные на рис. 6.31. Вместо ис- пользования операций запроса и отказа мы можем запретить прерывание по таймеру в промежутке между строками 24 и 27 программы Р1 и стро- ками 37 и 40 программы Р2. (Прерывания могут быть закрыты и открыты при помощи запросов на обслуживание операционной системе.) Может ли это быть практическим решением проблемы, рассмотренной в тексте? 8. Как операционная система может определить, что произошла взаимо- блокировка?
Глава 7. Другие компоненты системного программного обеспечения В этой главе мы вкратце рассмотрим некоторые другие ком- поненты системного программного обеспечения. Наша цель-ч ввести связанные с ними основные понятия и определения. По- зкэльку объем книги ограничен, мы не стремимся дать деталь- ное описание их реализаций. А для читателей, желающие глубже изучить эти вопросы, в конце каждого раздела приве- дены ссылки на соответствующую литературу. Раздел 7.1 посвящен описанию задач и функций обобщенных систем управления базами данных (СУДБ) и их взаимодей- ствию с операционными системами. В разд. 7.2 дается краткое описание диалоговых систем редактирования текстов, обсуждав ются заложенные в них основные идеи. В разд. 7.3 рассматриваются системы интерактивной отладки программ. Хотя потребность в таких системах уже давно изве- стна, практически их существует сравнительно мало. Поэтому в разд. 7.3 внимание уделяется тем функциям и характеристи- кам, которые в дальнейшем развитии программного обеспече- ния такого рода, возможно, окажутся наиболее существенными. 7.1. Системы управления базами данных В этом разделе будут рассмотрены основные задачи и функ- ции обобщенной системы управления базами данных (СУБД). СУБД нас будет интересовать главным образом с точки зрения пользователя. Мы рассмотрим также, как связаны между собой СУБД и другие компоненты программного обеспечения системы. Сравнительный анализ теоретических и практических реали- заций системы управления базами данных можно найти у Уидерхолда [1983], Дейта [1981], Ульмана [1980] и других! авторов. В разд. 7.1.1 обсуждаются проблемы, решение которых при- вело к созданию систем управления базами данных, а также вводится понятие СУБД. В разд. 7.1.2 в общих чертах расска- зывается о том, что представляют собой эти системы с точки зрения пользователя. В разд. 7.1.3 рассматривается вопрос о связи СУБД с другими компонентами системного про- граммного обеспечения, в особенности с операционными системами.
400 Гл. 7. Другие компоненты системного программного обеспечения 7.1.1. Основные концепции СУБД Разработка систем управления базами данных в большой степени явилась результатом решения двух основных проблем, возникших при работе с обычными системами файловой обра- ботки: избыточности и зависимости данных. Рассмотрим, напри- мер, рис. 7.1, на котором показаны в упрощенном виде несколь- ко систем файловой обработки для некоего гипотетического университета. Сюда входят: система регистрации, позволяющая следить за составом студентов на курсах, система финансовой помощи, контролирующая расход стипендиального фонда, си- стема платежных ведомостей для всего университета, включая профессорско-преподавательский состав и студентов, работаю- 1цих по найму. Рис. 7.1. Раздельные системы файловой обработки. Система финансовой помощи Каждая система файловой обработки состоит из ряда про- грамм и набора файлов данных. Формат, организация и содер- жимое последних задаются тем, кто с самого начала определяет систему. В файлах каждой системы содержится только не- обходимая ей информация. Согласование между файлами раз- личных систем либо незначительно, либо вовсе отсутствует. Использование автономных систем файловой обработки, аналогичных тем, что показаны на рис. 7.1, часто приводит к значительной избыточности данных, т. е. к дублированию эле- ментов данных в различных файлах. Например, число курсов, на которых числится студент, может храниться как в одном из файлов системы регистрации, так и в файле системы финан- совой помощи. А если к тому же студент работает в универси- тете по найму или получает стипендию, то его фамилия и ад- рес могут появиться в трех различных местах. Наиболее очевидным недостатком избыточности данных является необходимость использования дополнительной памяти. Однако существуют и более серьезные проблемы, связанные с дублированием данных. При изменении информации, если она хранится в нескольких местах, исправления должны быть сде- ланы несколько раз. Для этого требуется больше машинного
7.1. Системы управления базами данных 4п1 Программы Объединенная база данных 6 Рис. 7.2. Зависимые от данных программы, использующие объединенную базу данных. (То, что требует изменения при добавлении новой системы, заштриховано.) времени и операций ввода-вывода. Но еще серьезнее то, что существует вероятность несоответствия данных из-за ошибок в программе или различия в планировании при изменении фай- лов. Например, запись в файле регистрации может отражать тот факт, что некоторый студент отчислен с определенного кур- са; однако эта информация может быть не занесена в соответ- ствующие файлы системы финансовой поддержки. Одно из возможных решений проблемы избыточности пока- зано на рис. 7.2а. Информация для всех файловых систем собрана в объединенную базу данных для всего университета. В ней может содержаться только одна копия каждого логиче- ского элемента данных, поэтому избыточность исключается.
432 Гл. 7. Другие компоненты системного программного обеспечения Сама база данных состоит из набора файлов. Каждый из них может использоваться различными прикладными программами, которым требуется один и тот же элемент данных. Хотя только что описанный подход решает проблему избы- точности данных, он может спровоцировать и другие трудности. Прикладные программы, имеющие дело непосредственно с фи- зическими устройствами, зависят от данных. Речь идет об их зависимости от таких характеристик, как формат записи, ор- ганизация файла и порядок следования. При изменении харакл теристик приходится модифицировать и прикладные программы. Предположим, например, что к прикладным программам до- бавляется студенческая консультационная система. Ей может понадобиться определенная информация, отсутствующая в базе данных; следовательно, возникнет необходимость создания од- ного или нескольких новых файлов. С другой стороны, часть нужной информации уже имеется. Системе могут потребоваться такие элементы данных, как шифр специализации студента или сведения о текущем составе студентов данного курса. К сожалению, имеющаяся информация может быть пред- ставлена не в том виде, какой требуется новой системе. Пред- положим, например, что консультационная система должна обеспечить интерактивный доступ к информации о списке сту- дентов, имеющих определенного научного руководителя. Для этого требуется, чтобы к регистрационной информации о сту- денте была добавлена фамилия его научного руководителя, а регистрационные данные — индексированы фамилией руководи- теля. В результате должны быть изменены содержимое и ор- ганизация некоторых существующих файлов. Из-за зависимости данных все программы, в которых эти файлы используются, также должны быть откорректированы. Рассмотренный случай иллюстрируется рис. 7.26. Справоч- ная система использует три файла из базы данных — один но- вый и два уже существующих. Необходимо изменить формат и структуру одного из существующих файлов, используемых новой системой (на рисунке он заштрихован). Следовательно, все про- граммы, работающие с этим файлом, должны быть откорректи- рованы. В данном случае изменения коснутся программ в си- стемах регистрации и финансовой помощи. Решить только что рассмотренные проблемы можно, сделав так, чтобы прикладные программы не зависели от таких дета- лей, как организация файлов и формат записей. Как это можно сделать, показано на рис. 7.3а. Программы пользователей имеют дело не с самими файлами из базы даных, а делают запросы системе управления базой данных (СУБД) на логическом уров- не без учета того, как данные хранятся в файле. Например, программа может запросить текущую регистрационную инфор- мацию по определенному студенту. Посредством обращения к
7.1. Системы управления базами данных 403 Рис. 7.3. Не зависимые от данных программы, использующие систему управ* ления базами данных. (То, что требует изменения при добавлении новой системы, заштриховано.) описанию отображения данных СУБД определит нужные физи- ческие файлы и порядок доступа к ним. После чтения файлов содержащаяся в них информация будет приведена к нужному прикладной программе виду. Об этом более подробно говорится в разд. 7.1.2 и 7.1.3. Независимость данных, полученная с помощью только что описанного подхода, означает, что можно менять структуры .файлов, не трогая при этом прикладные программы. Рассмот- рим, например, рис. 7.36. Пусть, как и раньше, добавляется новая консультационная система; в базу данных включен но- вый файл, а один уже существующий модифицирован (на ри- сунке он заштрихован), Эти изменения повлекли за собой
404 Гл. 7. Другие компоненты системного программного обеспечения корректировку описания отображения данных, однако сами при- кладные программы остались без изменений. Как и раньше, по логическому запросу будет выполнен ряд операций над файла- ми базы данных. Однако прикладные программы об этом не узнают, потому что они имеют дело только с логическими эле- ментами информации, а не с тем, как информация хранится в файлах. Независимость данных, обеспечиваемая системой управления базами данных, важна и по другим причинам. При желании в любой момент могут быть заменены технические средства, на которых реализована база данных. Например, вся она или же ее часть может быть перенесена на запоминающее устройство другого типа. Файлы могут быть реорганизованы, пересортиро- ваны в различные последовательности или проиндексированы другим набором ключей. Обычно это делает администратор базы данных, который старается так организовать и хранить данные, чтобы база данных использовалась с большей эффек- тивностью. Все этим изменения можно сделать, не оказывая вли- яния на прикладные программы. На самом деле программы вообще не в состоянии определить, были ли внесены такие из- менения. Из-за того что каждое обращение к базе данных осуществля- ется при помощи описания отображения данных, использование СУБД приводит к большим накладным расходам системы, чем при выполнении программ, зависящих от данных. Однако вы- года, получаемая от независимости данных и сокращения их избыточности, обычно значительнее дополнительных накладных расходов. Это особенно справедливо в случаях, когда содержи- мое и структура базы данных являются предметом периодиче- ских изменений при добавлении новых приложений. 7.1.2. Уровни описания данных Как видно из предыдущего раздела, использование системы управления базами данных делает прикладные программы не-* зависимыми от физического способа их хранения. Если в обыч- ных файловых системах программист имеет дело с описаниями способов организации, создания последовательностей и индекси- рования файлов, то при работе с СУБД программисту все эти детали обычно не известны. Но, даже если он с ними и зна- ком, он не смог бы этим воспользоваться, поскольку физический носитель базы данных может быть заменен в любое время. Таким образом, прикладной программист должен иметь пред- ставление о данных на логическом уровне, не связанном никак со структурой файлов и другими вопросами физического хра-
7.1. Системы управления базами данных 405 Информация, хранимая СУБД, в зависимости от нужд поль- зователя может быть представлена по-разному. Наиболее общим представлением является полное логическое описание базы дан- ных, называмое схемой. На рис. 7.4 показан пример такой схемы. Она состоит из набора логических записей базы дан- ных, таких как СТУДЕНТ и КУРС. Некоторые из них соеди- нены линиями, отражающими возможные отношения между записями данных типов. Например, отношением «зачислен-на» задается, какой студент на какой курс зачислен. Предполагает- ся, что это пример базы данных для гипотетического универси- к Ате го рия-ди пл ом а курс Рис. 7.4. Схема для простой базы данных университета. тета. Элементы данных в логических записях дают представле- ние о типе содержащейся в них информации. В реальной базе данных, возможно, будет больше типов логических записей, а в каждой из них намного больше элементов данных. Системы управления базами данных сильно различаются в зависимости от типа записей и отношений, которые могут со- держаться в схеме. Например, в некоторых системах требуется, чтобы логическое представление базы данных имело иерархи- ческую или древовидную структуру, в то время как в других допускается наличие более общих типов отношений между записями. В некоторых системах явные связи между записями вообще запрещены. В этом случае отношения выражаются не- явно через значения соответствующих элементов данных. Об- суждение подобных моделей данных выходит за рамки нашей книги. Дальнейшую же информацию и ссылки можно найти в Дейт [1981]. Схема дает полное описание логической структуры и содер< жимого базы данных. Однако большинство прикладных про- граммистов используют лишь небольшую толику этой инфор- мации; конкретная программа обычно имеет дело только с несколькими типами записей и отношений. Описание данных»
406 Гл. 7. Другие компоненты системного программного обеспечения необходимое прикладной программе, дается подсхемой. Обычно у одной схемы существует много различных подсхем. Каждая из них дает представление о базе данных, соответствующей нуждам программ, которые используют эту подсхему. На рис. 7.5 показаны три подсхемы, соответствующие раз- личным частям схемы, приведенной на рис. 7.4. Подсхема а может быть использована программой, формирующей распечат- ку списка студентов по их специализации. Программе, исполь- зующей подсхему а, база данных кажется состоящей из неко- торого числа записей КАТЕГОРИЯ-ДИПЛОМА, каждая из ко- КАТЕГОРИЯ-ДМПЛОМА ЛРОФ.-ПРЁП.СОСТАВ СТУДЕНТ-КУРСА ин фамилия специализация! 6 ОПЛАТА ин ФАМИЛИЯ адрю с/п ВИД-РАБОТЫ ОКЛАД НАЛОГИ В Рис. 7.5. Три возможные подсхемы, соответствующие схеме на рис. 7.4. торых связана с набором записей СТУДЕНТ (одной для каж- дого студента данной специализации). Подсхема б может быть использована программой, печатающей списки групп каждого преподавателя. Представление базы данных в этом случае име- ет трехуровневую структуру. Каждая запись, соответствующая преподавателю, связана с набором записей, соответствующих курсам, на которых он (или она) преподает; каждая запись, соответствующая курсу, в свою очередь связана с набором записей списка студентов, числящихся на курсах. Подсхема в будет использоваться программой, обрабатывающей платежные ведомости всего университета. Каждая логическая запись в этой подсхеме содержит информацию, необходимую для выдачи служащему зарплаты. Эти три подсхемы дают совершенно различные представле- ния о базе данных. Подсхема должна быть согласована со схе- мой, т. е. должна существовать возможность извлекать инфор- мацию, имеющуюся в подсхеме, и из самой схемы. Подсхема а является просто ее подмножеством; имена записей, элементы
7.1 Системы управления базами данных 407 данных и отношения те же, что и в соответствующей части схемы. Однако это не обязательно верно для всех подсхем. В подсхеме б прикладные программы используют имена записей, отличные от тех, что содержатся в схеме. Возможно также использование иных имен и для других элементарных данных. Запись ПРОФ.-ПРЕП.-СОСТАВ в подсхеме б содер- жит только часть, а не всю информацию, соответствующую записи ПРОФ.-ПРЕП.-СОСТАВ в схеме. Запись ДИСЦИПЛИ- НА в подсхеме содержит всю информацию записи КУРС в схеме. Запись СТУДЕНТ-КУРСА в подсхеме включает инфор- мацию из записи СТУДЕНТ в схеме. Однако в запись СТУ- ДЕНТ-КУРСА входит также и информация о специализации студента, которая содержится в записи КАТЕГОРИИ-ДИПЛО- МА, логически связанной с записью СТУДЕНТ отношением «специализируется-в». Подсхема в состоит из единственной логической записи типа ОПЛАТА, данные в которой могут быть сформированы при по- мощи трех различных записей схемы. Информация, касающая- ся оклада и налогов, может быть взята из записи ПЛАТЕЖ- НАЯ-ВЕДОМОСТЬ, принадлежащей схеме. Такая информация, как фамилия служащего и его адрес, извлекается с использова- нием отношений «студент-по-найму» и «преподаватель-по-най« му» из записей либо СТУДЕНТ, либо ПРОФ.-ПРЕП.-СОСТАВ, какая больше для этого подходит. Поле данных в ОПЛАТА, обозначенное С/П, показывает, к кому запись имеет отноше- ние: к студенту, работающему по найму, или к преподавателю. Подсхема дает прикладной программе наиболее отвечающее ее нуждам представление о базе данных. За переводом инфор- мации, содержащейся в базе данных, в форму, заданную под- схемой, следит СУБД (разд. 7.1.3). В результате писать при- кладную программу проще и легче, потому что программисту не нужно использовать элементы данных и отношения, не от- носящиеся к самой прикладной программе. Кроме того, под- схема помогает в обеспечении защиты данных: программа не имеет возможности обращаться к элементам данных, не опи- санным в ее подсхеме. Итак, мы рассмотрели три различных уровня описания дан- ных в системе управления базами данных: подсхемы, схему и описание отображения данных. Для создания базы данных на каждом уровне СУБД применяются так называемые языки описания данных. Подсхемы, используемые прикладными про- граммистами, реализованы на удобном для них языке описания подсхем. Часто эти языки появляются в результате расширения возможностей описания данных в используемом языке програм- мирования. Подсхемы, однако, создаются и поддерживаются администратором базы данных. Определяя подсхему, он дол- жен быть уверен, что представление данных в подсхеме может
408 Гл. 7. Другие компоненты системного программного обеспечения быть получено из схемы и содержит только те элементы дан- ных, которые разрешено использовать данной прикладной про- грамме. Сама схема и описание отображения физических данных обычно используются только администратором базы данных, ©о многих системах язык описания схем тесно связан с языком описания подсхем. Поскольку схема непосредственно приклад- ными программами не используется, возможно также исполь- зование и более общего языка. На язык описания физических данных влияют типы логических структур, поддерживаемых схе- мой, а также типы файлов и запоминающих устройств, поддер- живаемых СУБД. Дальнейшее обсуждение и примеры языков описания дан- ных можно найти в Дейт [1981]. 7.1.3. Использование СУБД В двух предыдущих разделах мы ввели основные понятия и терминологию по системам управления базами данных. В этом разделе мы завершим описание СУБД, для чего рассмотрим их взаимодействие с пользователями и связь СУБД с другими компонентами системного программного обеспечения. Два основных способа взаимодействия пользователя с СУБД показаны на рис. 7.6. Пользователь может написать исходную программу обычным способом, используя общецелевые языки программирования, такие как Кобол, ПЛ/1 или ассемблер. Од- нако вместо имеющихся в этих языках операторов ввода-выво- *да программист пишет команды на языке манипулирования данными (ЯМД), который предназначен для взаимодействия пользователя с СУБД. Эти команды часто создаются так, что- бы ЯМД казался простым расширением языка программирова- ния. Как показано на рис. 7.6а, для перевода команд ЯМД в операторы языка программирования, которые вызывают про- граммы СУБД, может быть использован препроцессор. После этого модифицированная исходная программа компилируется стандартным способом. Другой подход заключается в том, что- бы изменить компилятор так, чтобы он сам обрабатывал опе- рации ЯМД. Некоторые языки управления данными определя- ются как набор операторов CALL, используемых в языке про- граммирования,^ в результате чего отпадает надобность в препроцессировании или модификации компилятора. Другой подход к взаимодействию с СУБД, показанный на рис. 7.66, не требует от пользователя написания программы для осуществления доступа к базам данных. Вместо этого поль- зователи вводят команды на специальном, задаваемом СУБД языке запросов. Эти команды обрабатываются интерпретатором
7.1. Системы управления базами данных 409 программы. 6. а Рис. 7.6. Взаимодействие с СУБД посредством языка манипулиро- вания данными (а) и языка за- просов (б). языка запросов, который для выполнения требуемых операций вызывает программы СУБД. Оба подхода имеют свои преимущества. Использование язы- ка запросов позволяет получить результаты гораздо быстрее, поскольку нет необходимости пис Языки запросов могут с успехом применяться непрофессиональ- ными программистами и теми, кто программирует лишь от слу- чая к случаю. Однако большин- ство языков запросов содержит встроенные ограничения. Напри- мер, бывает сложно или вообще невозможно выполнить функцию, на которую язык не рассчитан. С другой стороны, ЯМД позво- ляет программисту использо- вать гибкость и все возмож- ности языков программирова- ния общего назначения; однако этот подход потребует от поль- зователей гораздо больших уси- лий. В большинстве современ- ных систем управления базами данных имеется как язык за- просов, так и ЯМД; при необхо- димости пользователь выбирает удобную для него форму взаимо- действия. Дальнейшую информа- цию о ЯМД, языках запросов и их примеры можно найти в Дейт [1981]. При обработке запроса СУБД выполняет одну и ту же по- следовательность операций вне зависимости от того, использу- ется язык запросов или ЯМД. Эти действия показаны на рис. 7.7. Начало последовательности событий приходится на момент, когда посредством запроса от прикладной программы А осуществляется обращение к СУБД (шаг 1 на рисунке). Если используется язык запросов, то программа А является интер- претатором языка запросов. Предполагается, что запросом яв- ляется требование чтения данных из базы данных. Последова- тельность событий для других операций базы данных анало- гична. Запрос от программы А формируется в терминах использу- емой ею подсхемы. Например, программа, использующая под- схему, приведенную на рис. 7.5в, может запросить для задан-
410 Гл. 7. Другие компоненты системного программного обеспечения ного служащего запись ОПЛАТА. Чтобы обработать этот за* прос, СУБД сначала должна исследовать используемое опреде- ление подсхемы (шаг 2). Она также должна рассмотреть от- ношения между подсхемой и схемой (шаг 3), чтобы интерпре- тировать запрос в терминах всей логической структуры базы данных. Так, например, СУБД обнаружит, что для того, чтобы снабдить программу А ожидаемой ею записью ОПЛАТА, ей Рис. 7.7. Типичная последовательность выполняемых СУБД действий. (За- имствовано из Martin J. Computer Data — Base Organization, 2nd ed., C. 1977, p. 83. Reprinted by permission of Prentice-Hall Inc., Englewood Cliffs, N. J.) потребуется прочитать запись ПЛАТЕЖНАЯ-ВЕДОМОСТЬ схе- мы для данного служащего (см. рис. 7.4). Вдобавок СУБД потребуется для записи ПЛАТЕЖНАЯ-ВЕДОМОСТЬ, о кото- рой идет речь, изучить отношения «студент-по-найму» и «пре- подаватель-по-найму» и прочитать соответствующие записи СТУДЕНТ и ПРОФ.-ПРЕП.-СОСТАВ После определения логических записей базы данных, кото- рые должны быть прочитаны (в терминах схемы), СУБД изу- чает описание отображения данных (шаг 4). Эта операция дает информацию, необходимую для нахождения нужных записей в файлах базы данных. В этот момент СУБД уже пе- ревела логический запрос записи подсхемы в физический на чтение данных из одного или нескольких файлов. Эти запросы
7.2. Текстовые редакторы 411 для файла ввода-вывода передаются операционной системе (шаг 5) с использованием запросов на обслуживание, рассмот- ренных в гл. 6. Затем операционная система выдает команды каналу и устройству для выполнения необходимых операций ввода-вывода (шаг 6). В результате осуществляется перенесе- ние требуемых записей из базы данных в буферную область СУБД. После того как завершено выполнение физических операций, все данные, запрошенные прикладной программой, находятся в центральной памяти. Однако эта информация еще должна быть преобразована в нужную программе форму. И снова СУБД делает это посредством сравнения схемы с подсхемой. В рассматриваемом нами примере СУБД извлечет данные из записи ПЛАТЕЖНАЯ-ВЕДОМОСТЬ и соответствующих запи- сей СТУДЕНТ и ПРОФ.-ПРЕП.-СОСТАВ и создаст запись ОПЛАТА, запрашиваемую программой А. Запись ОПЛАТА за-? тем будет помещена в рабочую область, заданную прикладной программой; на этом обработка программного запроса данных завершается. Наконец, СУБД возвратит управление прикладной программе, предоставив ей все многообразие информации о со- стоянии, включая указания о возможных ошибках. Подробнее о затронутых в разделе вопросах можно прочи- тать в У идерхолд [1983] и Дейт [1981]. 7.2. Текстовые редакторы0 Интерактивный текстовый редактор стал важной частью практически любой вычислительной системы. Редакторы более не считаются орудиями труда только программистов или секре- тарей, вносящих изменения в соответствии с новым авторским вариантом текста. Сейчас текстовый редактор получает все большее признание в качестве основного средства общения с ЭВМ «работников умственного труда» любого типа в те пери- оды, когда они создают, организуют, изучают и обрабатывают информацию, заложенную в ЭВМ. В этом разделе мы рассмотрим интерактивные системы тек- стового редактирования с точки зрения как пользователя, так и системы. В разд. 7.2.1 дается общее описание процесса ре- дактирования. В разд. 7.2.2 в дополнение к этому рассматрива- ются различные типы устройств ввода-вывода и средств обще-* Использованы работы Norman Meyrowitz and Andreis van Dam, «Interactive Editing Systems: Part I and Part II», ACM Computer Surveys, Sep* tember, 1982. Copyright 1982, ACM, Inc. В этих работах также содержится значительно более подробный материал о процессе редактирования, описана большое число существующих редакторов и приведена обширная библио? графия, — Прим, ред.
412 ' Гл/7. Другие компоненты сйГбТемЯбго программнбго обёспёчейия^ ния с пользователем. В разд. 7.2.3 описывается структура ти« пичного текстового редактора и рассматривается ряд вопросов, касающихся его системной организации. 7.2.1. Описание процесса редактирования Интерактивный редактор — это программа, с помощью кото- рой пользователь может создавать и изменять целевой доку- мент. В понятие документа входят такие объекты, как про-< граммы, тексты, формулы, таблицы, диаграммы, чертежи и фо- тографии — все то, что может находиться на печатной страни- це. Здесь мы ограничимся рассмотрением текстовых редакторов. в которых первичными редактируемыми элементами являются строки символов в целевом тексте. Процесс редактирования документа — это диалог между пользователем и ЭВМ, во время которого требуется решить че- тыре задачи. 1. Выбрать часть целевого документа для отображения и об- работки. 2. Определить, каким образом отформатировать и визуализи- ровать полученный образ. 3. Задать и выполнить операции, изменяющие целевой доку- мент. 4. Изменить в соответствии с этим образ. Выбор части документа для отображения и редактирования включает первый просмотр всего документа для нахождения нужной области. Поиск производится с помощью таких опера- ций, как выдать следующую (предыдущую) страницу и найти по образцу. Во время просмотра определяется, где находится нужная область; выбор объекта для отображения и работы осуществляется посредством операции фильтрации. С ее по- мощью выделяется некоторое подмножество целевого докумен- та, например страница или оператор. Затем посредством фор- матирования создается визуальное представление (образ), ко- торое отобразит результат фильтрации на экране дисплея или на бумаге. В реальном процессе собственно редактирования целевой документ создается и изменяется при помощи ряда операций, .таких как вставить, уничтожить, заменить, передвинуть и ко- рировать. Функции редактирования часто ориентированы на ра- боту с элементами, зависящими от типа редактора. Например, редактор, ориентированный на работу с рукописями, может оперировать такими элементами, как отдельные символы, слова, строки, предложения и абзацы; программно-ориентированный редактор может работать с идентификаторами, ключевыми сло< вами и операциями.
7.2. Текстовые редакторы 413 В простейшем случае пользователь может просмотреть до- кумент до конца. Последняя страница текста будет отфильтро- вана, затем отформатирована, а полученный образ — выведен на внешнее устройство. После этого пользователь может, на- пример, уничтожить в образе первые три слова. 7.2.2. Интерфейс пользователя Пользователю интерактивного редактора предоставляется концептуальная модель системы редактирования, т. е. некоторая абстрактная основа, на которой базируется и сам редактор, и все, с чем он работает. В сущности, простота понимания абст- рактного представления целевого документа и его элементов достигается в концептуальной модели при помощи набора прин- ципов, которые описывают конечный результат операций редак- тирования. В некоторых ранних строчных редакторах моделиро- валась работа на клавишных перфораторах. В них существова- ли операции над пронумерованными последовательностями 80-символьных строк-образов перфокарт. Операции выполня- лись либо в пределах одной строки, либо над несколькими строками. В некоторых более современных экранных редакто- рах среда, в которой представлен документ, имеет вид квадра- та, не ограниченного ни справа, ни снизу. Операции выполня- ются над его частями без учета границ строк. Через «окно» на дисплее пользователь видит только прямоугольное подмноже- ство квадрата. Окно может передвигаться вправо и влево, вверх и вниз, при этом визуализируются соответствующие части документа. Помимо концептуальной модели к интерфейсу пользователя относятся устройства ввода и вывода, а также диалоговый язык системы. Освещению этих аспектов посвящена оставша- яся часть раздела. Устройства ввода служат для ввода элементов редактируе- мого текста, для введения команд и для задания редактируе- мых элементов. Эти устройства при использовании их редакто- ром можно разделить на три категории: устройства для ввода текста, клавишные устройства и координатные манипуляторы. К устройствам для ввода текста или строк символов относится клавиатура (как у пишущей машинки), на которой пользовате- ли нажатием и отпусканием клавиш вызывают генерирова- ние соответствующих кодов. Фактически все имеющиеся в на- стоящее время клавиатуры ЭВМ — системы QWERTY (первые шесть букв второго ряда клавиатуры). Предлагались и другие конструкции клавиатур, некоторые из которых имели значитель- ные преимущества по сравнению со стандартными. Однако воз- можно, что ни одна из них не найдет широкого применения в
414 Гл. 7. Другие компоненты системного программного обеспечения ближайшем будущем, поскольку для этого потребуются значи- тельные усилия по переучиванию. Клавишные устройства или устройства выбора вызывают прерывание или установку системного флага, что приводит к выполнению прикладной программой определенных действий. К таким устройствам обычно относятся функциональные клави- ши, имеющиеся на алфавитно-цифровой клавиатуре или на са- мом дисплее. Клавиши могут моделироваться и программно — посредством визуализации строк текста или символов. Вместо нажатия на клавишу пользователь выбирает строку или сим- вол. Координатные манипуляторы — это двумерные аналого-циф- ровые преобразователи, отслеживающие действия, выполняемые пользователем, и устанавливающие в зависимости от этого кур- сор на экране. К таким устройствам относятся координатная ручка управления курсором, экранные сенсорные панели, план- шетки и «мышка». Последние два устройства чаще всего используются для редактирования. Планшетка — это плоская прямоугольная панелька, обладающая электромагнитной чувст- вительностью. По ее поверхности перемещается либо игла, похо- жая на шариковую авторучку, либо шайба — маленькое устрой- ство, умещающееся в ладони. Планшетка передает системной программе текущие координаты иглы или шайбы. Программа может затем перевести эти координаты в экранные и поместить курсор в соответствующее место. «Мышка» также умещается в руке и похожа на шайбу. Ее движение по гладкой поверхности вызывает относительное изменение местоположения, которое сравнивается системной программой с некоторым образцом. Ко- ординаты «мышки» опять-таки преобразуются в экранные, что приводит к перемещению курсора. Координатный манипулятор, совмещенный с клавишным устройством, позволяет задавать либо определенную точку на экране, начиная с которой должен быть введен или уничтожен текст, либо начальную и конечную точки строки символов, ко- торая будет обрабатываться. Фактически «мышка» или шайба Имеет встроенные клавиши, позволяющие пользователю сигна- лизировать о выборе. Когда курсор указывает на нужный эле- мент, пользователь сообщает об этом нажатием клавиши. Си- стема сопоставляет местоположение курсора с элементом и вы- полняет требуемые действия. Для моделирования координатного манипулятора часто ис- пользуют устройства для ввода текста со «стрелочными» (кур- сорными) клавишами. На каждой из них изображена стрелка, Направленная вверх, вниз, влево, вправо. Нажатие на эти кла- виши вызывает генерирование соответствующей последователь- ности кодов; программа опознает ее и передвигает курсор в направлении, указанном стрелкой.
7.2. Текстовые редакторы 415 Все еще находящиеся в стадии исследования устройства ввода голосом, преобразующие произносимые слова в тексто- вые эквиваленты, могут стать в будущем устройствами для ввода текста. Пока еще ограниченные малым словарем (содер-. жащим менее 1000 слов) анализаторы голоса, вероятно, скоро приобретут коммерческую жизнеспособность. Устройства вывода, используемые при редактировании и пер- воначально ограниченные в области применения, становятся все более разнообразными. Они нужны для того, чтобы пользова- тель видел то, что он редактирует, а также результаты выпол- нения операций редактирования. Первыми устройствами вывода были (ныне устаревшие) телетайпы и другие литерно-печатаю- щие терминалы, использовавшие для вывода бумагу. Следую- щие, «стеклянные телетайпы» создавались на основе электрон- но-лучевых трубок (ЭЛТ). Они имели ЭЛТ в качестве экрана и, в сущности, моделировали работу обыкновенного телетайпа (хотя некоторые операции, например возврат назад, были реа- лизованы более изящно). В современных усовершенствованных ЭЛТ-терминалах существует аппаратная поддержка для таких операций, как перемещение курсора, вставка и уничтожение символов и строк, выдача строк и страниц. Новые автоматизиро- ванные рабочие места, основанные на персональных ЭВМ с дисплеями, обладающими высокой разрешающей способностью, имеют многочисленные пропорциональные символьные шрифты для выдачи реальных факсимиле твердых копий документов. Таким образом, пользователь может получить представление о том, как документ будет выглядеть на бумаге. Существует несколько типов диалоговых языков текстового редактирования. Самыми старыми способами общения редакто- ра с пользователем являются набивка или ввод текстовых команд. Пользователь набивает в виде текстовых строк как сами команды, так и операнды. Все это пересылается редакто- ру, а затем обычным образом отображается на устройстве вы- вода. При набивке спецификаций пользователь должен знать точ- ный вид всех команд или по крайней мере их аббревиатуру. Если командный язык сложен, пользователь должен постоянно обращаться за описанием наиболее редко встречающихся команд к руководству по системе или запрашивать помощь у машины. Вдобавок набивка отнимает много времени, особенно у неопытного пользователя. На устранение этих недостатков направлено использование функциональных (function-key) кла- виш. При этом каждой команде соответствует клавиша на кла- виатуре. Например, команде «вставить символ» (insert character) может соответствовать клавиша с надписью IC. Задание коман- ды, определяемой функциональной клавишей, обычно совмещено
416 Гл. 7. Другие компоненты системного программного обеспечения с перемещениями курсора, что значительно ограничивает на- бивку. Для ввода часто встречающихся команд редактора обычно требуется просто нажатие одной клавиши. Задание редко ис- пользуемых команд и дополнительных возможностей может быть осуществлено при помощи нажатия нескольких клавиш. Чаще, однако, для изменения стандартного значения функцио- нальных клавиш используются дополнительные клавиши особо- го вида (такие как SHIFT на печатающей машинке, переклю- чающая регистры). Или же можно заставить алфавитно-цифро- вую клавиатуру моделировать работу функциональных клавиш. Например, пользователь, нажав управляющую клавишу одно- временно с обычной, алфавитно-цифровой, вызовет генерирова- ние нового символа, который будет воспринят как функцио- нальный. Для работы с системами, ориентированными на набивку \typing-oriented systems), требуется хорошее знание системы и языка, умение печатать на машинке. В системах, ориентирован- ных на функциональную клавиатуру, часто используется либо совсем мало клавиш (при этом для формирования команд нуж- но многократное их нажатие), либо слишком много уникальных клавиш, в результате чего загромождается клавиатура. В лю- бом случае при работе с такими системами от пользователя требуется еще большая ловкость, чем при использовании стан- дартной клавиатуры. На решение этой проблемы направлено использование интерфейса, ориентированного на меню. Меню — это набор текстовых строк или образов, выраженных графиче- скими символами, изображающими объекты или операции. Пользователь может задавать нужные действия, выбирая их из меню. В меню, выдаваемом пользователю редактором, перечис- лены только те действия, которые могут быть предприняты в текущем состоянии системы. Сложности, касающиеся систем, ориентированных на меню, могут возникнуть, когда имеется много возможных действий и несколько путей выполнения каждого из них. Места для меню на дисплее обычно отведено мало; поэтому, прежде чем появит- ся описание требуемой команды и ее дополнительных возмож- ностей, пользователю будет последовательно выдано несколько меню в соответствии с иерархией. Поскольку квалифицирован- ному пользователю это может показаться скучным и ненужным, в некоторых системах допускается отключение управления меню (menu-control) и возврат к набивке и функциональным клави- шам. В других системах существует главное меню команд, со- держащее чаще всего используемые функции, и второстепенные меню, где перечисляются прочие функции. Есть системы, в ко- торых меню выводится только тогда, когда пользователь об этом специально попросит. Например, для вывода меню с пол-
7.2. Текстовые редакторы 417 ним перечнем возможных команд (возможно, с временным за- тиранием информации на экране) пользователь может нажать кнопку на «мышке». Сама «мышка» может быть использована для выбора нужной команды. Затем система выполнит коман- ду, а меню уничтожит. Такого рода средства, где подсказка и меню выдаются пользователю за небольшую дополнительную плату и с незначительным ухудшением времени ответа, приоб- ретают все большую популярность. 7.2.3. Структура редактора Большинство текстовых редакторов вне зависимости от их особенностей и машин, на которых они реализованы, имеют структуру, аналогичную той, что показана на рис. 7.8. Блок «ниня*..Управление Данные Рис. 7.8. Типичная структура редактора. (Заимствовано из Meyrowits van Dam A., «Interactive Editing Systems: Part I and Part II», ACM Com- puting Surveys, 1982. Copyright 1982, Association for Computing Machinery, Inc.) обработки команд воспринимает все, что вводится с устройства пользователя, и анализирует признаки и синтаксическую струк- туру команд. В этом смысле функции данного блока очень по- хожи на те, что выполняются в лексической и синтаксической фазах компиляции. Как и при компиляции, семантические про- граммы могут вызываться непосредственно блоком обработки команд. В текстовом редакторе на семантические программы возложены такие функции, как редактирование и отображение. 14 Зак. 792
418 Гл. 7. Другие компоненты системного программного обеспечения Блок обработки команд может сформировать необходимые опе- рации редактирования и в промежуточном представлении. За- тем оно будет расшифровано интерпретатором, который и вызо- вет нужные семантические программы. Использование проме- жуточного представления позволяет редактору применять для разных языков диалога с пользователем один и тот же набор семантических программ. Семантические программы реализуют функции просмотра, редактирования, отображения и визуализации. Операции ре- дактирования всегда явно задаются пользователем; операции визуализации задаются неявно прочими тремя категориями операций. Однако операции просмотра и отображения могут вызываться либо пользователем в явном виде, либо неявно опе- рациями редактирования. Отношения между этими классами операций могут быть гораздо более сложными, чем описано в разд. 7.2.1. В частности, между тем, что содержится на экране, и тем, что может быть отредактировано, не обязательно долж- но быть только одно отношение. Чтобы продемонстрировать это, рассмотрим подробнее на рис. 7.8 те блоки, которые имеют принципиальное значение. При редактировании документа начало редактируемой обла- сти задается текущим указателем редактирования, которым и управляет блок редактирования. Последний является объедине- нием модулей, участвующих в редактировании. Указатель мо- жет быть сброшен или установлен в явном виде пользователем (например, посредством таких команд просмотра, как выдать следующий абзац или выдать следующую страницу), а также неявно системой как побочный эффект от предыдущих опера- ций редактирования (например, уничтожить абзац). Блок про- смотра в редакторе выполняет реальную установку текущих значений указателей редактирования и отображения. Тем са- мым определяется точка, с которой при отображении или ре- дактировании начинается фильтрация. При вводе пользователем команды редактирования соответ- ствующий блок запускает фильтр, посредством которого на ос- нове текущего значения указателя редактирования и парамет- ров редактирующего фильтра создается новый буфер редакти- рования. В параметрах, задаваемых как пользователем, так и системой, содержится информация о границах текста, участвую- щего в операции. Фильтрация может заключаться просто в по- следовательном выборе смежных символов, начиная с текущей точки. Или же она может зависеть от более сложных условий, задаваемых пользователем и имеющих отношение к содержимо- му и структуре документа. В этом случае собираются различ- ные части документа, в том числе и несмежные. После этого семантические программы оперируют уже с буфером редакти- рования, являющимся, по существу, отфильтрованным подмно-
7.2. Текстовые редакторы 419 жеством структуры данных документа. Заметим, что наше опи- сание носит умозрительный характер. В действительности филь- трация и редактирование могут быть разделены, а буфер в яв- ном виде не создан. Аналогично при отображении документа начало отображае- мой области задается текущим значением указателя отображе- ния. Он поддерживается блоком отображения редактора, состо- ящим из модулей, предназначенных для формирования следую- щего образа. Указатель может быть установлен и сброшен либо самим пользователем посредством команды просмотра, либо Рис. 7.9. Простая взаимосвязь между буферами редактирования и отобра- жения. неявно системой как результат предыдущей операции редакти- рования. Когда содержимое экрана требуется обновить, блок отображения запускает соответствующий фильтр, с помощью которого на основе текущего значения указателя отображения и параметров фильтра создается буфер отображения. Парамет- ры могут быть заданы как пользователем, так и системой. В них содержится информация о количестве символов, необхо- димых для заполнения экрана, и о том, как их выбирать из документа. В строчных редакторах буфер отображения может содержать текущую строку, а в экранных редакторах — прямо- угольный кусок текста. Буфер отображения поступает в распо- ряжение блока визуализации, при помощи которого содержимое буфера переносится на прямоугольный участок экрана, называ- емый окном или полем индикации. Буфера редактирования и отображения, оставаясь независи- мыми, могут быть связаны по-разному. В простейшем случае они совпадают: пользователь редактирует текст прямо на эк- ране (рис» 7.9)» С другой стороны, оба буфера могут быть 14*
420 Гл. 7. Другие компоненты системного программного обеспечения полностью разделены. Например, при редактировании пользова- тель может дойти до строки 75 и, после того как она будет ото- бражена, захотеть изменить в строках с 1 по 50 все вхождения словосочетания «гадкий утенок» на слово «лебедь». Для это- го он использует такую команду, как change (изменить): [1, 50] с/гадкий утенок/лебедь/ Одним из последовательно осуществляемых по этой команде действий будет неявный переход к первой строке файла. Затем выбранные из документа строки с 1 по 50 будут перенесены в буфер редактирования. Здесь и осуществятся последующие за- мены без соответствующей модификации образа. В случае об- наружения образца текущие значения указателей редактирова- ния и отображения будут соответствовать последней из строк, в которой он был найден, а сама строка по умолчанию будет занесена в буфера редактирования и отображения. Если образец не найден, то в обоих буферах останется строка 75. Буфера редактирования и отображения могут частично пе- рекрываться или же один из них может полностью содержать- ся в другом. Например, пользователь может затребовать поиск конца документа, начиная с символа в центре экрана. В этом случае фильтром редактирования создается буфер, куда поме- щается текст документа, начиная от заданного символа и до конца документа. Буфер отображения содержит лишь ту часть документа, которая видна на экране. При этом только ее конец находится в буфере редактирования. Окна обычно занимают либо весь экран, либо его часть. Перенос содержимого буферов отображения в окна, занимаю- щие часть экрана, особенно удобен для редакторов современ- ных автоматизированных рабочих мест, оснащенных графиче- скими устройствами. Подобные системы обслуживают много окон, где одновременно отображаются различные части одного или нескольких файлов. Такой подход позволяет пользователю выполнять операции внутрифайлового редактирования с гораз- до большей эффективностью, чем в системах с единственным окном. Перенос содержимого буфера отображения в окно осуще- ствляется двумя блоками системы. Первый из них — блок ото- бражения — формирует идеальный образ, обычно имеющий не- зависимое от устройства вывода промежуточное представление. Образ может быть очень простым и состоять из текста, уме- щающегося в окне и организованного так, чтобы слова в стро- ках не обрывались посередине. Другая крайность — точная ко- пия отформатированной и распечатанной страницы текста со всеми формулами, таблицами и рисунками. Второй блок—• блок визуализации — переносит идеальный образ, полученный
7.2. Текстовые редакторы 421 от блока отображения, на физическое устройство вывода наи< более эффективным образом. Внесение изменений на полноэкранном дисплее, подключен- ном к ЭВМ через низкоскоростные линии связи (1200 бод а меньше), производится медленно, если любая коррекция требу- ет обновления всего экрана. В связи с этим большая исследо- вательская работа ведется по созданию оптимальных алгорит- мов экранного редактирования, основанных на сравнении теку- щего содержимого экрана с последующим. В них используются такие внутренние возможности терминала, как внесение и уда- ление символа. А передаются дисплею только те символы, ко- торые нужны для создания правильного изображения. Независимые от устройства операции ввода и вывода позво- ляют упростить диалоговый язык. Отделение операций редакти- рования и отображения от функций визуализации делает не- нужным создание разных версий редактора для каждого и? устройств. Во многих редакторах используются базы данных управления терминалами. Вместо того чтобы держать последо- вательности управления в программах визуализации в явном виде, эти редакторы просто обращаются к терминально-незави- симым библиотечным программам, таким как считать положе- ние курсора (read cursor position) и сместить на строку вниз (scroll down). Такие библиотечные программы при выборе под- ходящей управляющей последовательности для конкретного ди- сплея используют базу данных управления терминалами. Сле- довательно, добавление нового терминала сводится к занесению в базу данных соответствующего описания. Блоки редактора работают с документом пользователя, на- ходящимся как в основной памяти, так и в файловой системе на диске. Загрузка всего документа в основную память может оказаться невозможной; если же загружена только его часть, а выполнение большого числа задаваемых пользователем опе- раций влечет за собой чтение с диска, то редактирование мо- жет осуществляться недопустимо медленно. В некоторых систе- мах эта проблема разрешается посредством переноса всего файла в виртуальную память с предоставлением операционной системе возможности эффективного осуществления размещения страниц по запросу. Или же реализуются входящие в редактор программы страничного обмена, которые по необходимости чи- тают в память одну или более логических частей документа. Эти части нередко называют страницами, хотя между ними и страницами виртуальной памяти, а также твердыми копиями документа обычно ничего общего нет. Страницы остарт(?я в ос- новной памяти до тех пор, пока по требованию пользователя не будет загружена другая часть документа. Во внутреннем представлении системы документ организован не как последовательность строк и символов, а как структура
422 Гл. 7. Другие компоненты системного программного обеспечения данных редактора, позволяющая осуществлять добавле- ние, уничтожение и модификацию при помощи минимума опе- раций ввода-вывода и перемещений символов. На диске доку- мент хранится либо в виде такой же структуры данных, либо в независимом от редактора общецелевом формате. В послед- нем случае текст состоит из символьных строк, перемежающих- ся управляющими символами, такими как конец строки (line- feed) и табуляция (tab). Редакторы функционируют в трех различных вычислитель- ных средах: разделения времени, автономной и распределенной. Каждая из них накладывает свои ограничения на структуру редактора. В системе с разделением времени редактор должен работать интенсивно, несмотря на загрузку процессора ЭВМ, центральной памяти и устройств ввода-вывода. В автономной системе редактор должен иметь доступ к тем же функциям, что и в системе разделения времени. Этого можно частично добиться использованием небольшой локальной операционной системы. Или же функции могут быть встроены непосредствен- но в редактор, если автономная система ориентирована на ре- дактирование. В локальной распределенной сети с разделением ресурсов редактор должен работать независимо на любой ма- шине пользователя (как в автономной системе), а также уча- ствовать в разделении ресурсов, например файлов (как в си- стеме с разделением времени). Некоторыми системами редактирования с разделением вре- мени используются аппаратные средства самих терминалов. Та- кого рода интеллектуальные терминалы содержат микропроцес- соры и локальную буферную память, в которой может осуще- ствляться редактирование. Небольшие редакции выполняются не под управлением ЦП ведущего процессора, а самими терми- налами. Например, редактор может передать текст объемом с экран от ведущего процессора терминалу. Далее пользователь волен добавлять или уничтожать символы и строки, используя для этого буфер и управляющие команды терминала. После того как буфер отредактирован, его обновленное содержимое передается назад, ведущему процессору. Преимущество такого подхода в том, что ЭВМ не касаются никакие незначительные изменения или нажатия клавиш; но это и основной недостаток. Если используется неинтеллектуаль- ный терминал, ЦП видит каждый набиваемый символ и может немедленно отреагировать на него, исправив ошибку, выдав подсказку, изменив структуру данных и т. п. Что касается ин- теллектуального терминала, то недостаток постоянного контро- ля со стороны ЦП часто означает ограниченность в функцио- нальных возможностях, предоставляемых пользователю. В про- цессе автономной работы на таком терминале пользователь может пропустить момент выхода системы из строя, С другой
7.3. Системы интерактивной отладки 423 стороны, в системах, где ввод каждого символа вызывает пре- рывание ЦП, весь набор аппаратных средств редактирования терминала может не использоваться в связи с тем, что ЦП не- обходимо отслеживать каждое нажатие клавиши и обеспечить эхо-контроль. 7.3. Системы интерактивной отладки ' Системы интерактивной отладки призваны помочь програм- мистам в тестировании и отладке их программ. В настоящее время существует ряд интерактивных отладчиков; однако ши- рокое распространение получили лишь некоторые из них. Зна- чение подобного программного изделия оценивается противо- речиво. Неясно, в какой степени низкий процент использования отладочных средств объясняется функциями и поведением са- мих отладчиков, а в какой — незнанием их возможностей поль- зователями. Тем не менее очевидно, что практически любой пользователь испытывает нужду в удобных средствах интерак- тивной отладки. В этом разделе рассматриваются некоторые наиболее важные требования, предъявляемые пользователями к отладчикам, а также ряд основных соображений по организа- ции подобных системных услуг. Ниже излагаются результаты, полученные во время совмест- ной работы инициативной группы GUIDE/SHARE Language Fu- tures Task Force (LFTF) и IBM. Группа LFTF была обра- зована в результате деловых встреч двух основных групп пользователей SHAPE и GUIDE в 1979 г. В ее задачи входило предоставить фирме IBM информацию о взглядах пользовате- лей на будущее прикладных средств. Одно из направлений ис- следований было связано с интерактивной отладкой. В работе Сейднер [1983] говорится о том, как фирма IBM расценивает требования, предъявляемые к отладке, которые были сформи- рованы группой LFTF. Все требования умышленно имеют об- щий характер и не ограничиваются архитектурой System/370, каким-то одним программным продуктом или операционной си- стемой. В разд. 7.3.1 дается общее представление об основных функ- циях и возможностях систем интерактивной отладки, рассмат- риваются некоторые сопутствующие проблемы. В разд. 7.3.2 описывается связь отладочных средств с другими элементами системы. Раздел 7.3.3 посвящен описанию интерфейса пользова- теля в системах интерактивной отладки. ° Использованы работы Rich Seidner and Nick Tindall, «Interactive Debug Requirements». SOFTWARE ENGINEERING Notes and SIGPLAN NOTICES, August 1983. Copyright 1983, Association for Computing Machinery, Ince
424 Гл. 7. Другие компоненты системного программного обеспечения 7.3.1. Функции и возможности отладчиков Ниже рассматриваются наиболее важные функции систем интерактивной отладки. Реализация некоторых из них более сложна по сравнению с остальными. В отдельных случаях не совсем ясны пути наилучшей реализации. Наиболее очевидными являются требования, предъявляемые к задаваемым самим пользователем функциям тестирования устройств (unit test functions). Среди них значительная группа связана со слежением за выполнением, т. е. за наблюдением ц контролем за ходом выполнения программы. Например, про- граммист может установить точки останова, по достижении ко- торых происходит останов программы. В результате появится возможность проанализировать ее состояние и провести диаг- ностику обнаруженных ошибок. Затем выполнение программы может быть продолжено. Или же программист может задать условные выражения, которые затем непрерывно вычисляются в процессе выполнения. Как только какое-нибудь из них при- обретает значение «истина», выполнение программы останавли- вается. Аналогично может быть реализован останов программы в результате выполнения заданной последовательности команд. Если же функционирование программы может быть представ- лено графически, имеет смысл попробовать прогнать ее с раз- личными скоростями, называемыми аллюром (gaits). В системах отладки должны быть реализованы и такие функции, как трассировка и обратная трассировка. Трассировка (tracing) позволяет следить за ходом выполнения логических операций и за изменением данных. Она может осуществляться на разных уровнях детализации: модульном, подпрограмм, команд ветвлений и т. п. Трассировка может также основывать- ся на вычислении условных выражений, о чем говорилось выше. Обратная трассировка (traceback) может показать маршрут, который привел к текущему оператору. С ее помощью можно увидеть, какой оператор изменил значение данной переменной или параметра. Такая информация должна быть представлена в символической форме. Например, скорее следует выдавать номера операторов, а не их шестнадцатеричное смещение 1>. В отладочной системе важно иметь также хорошие средства отображения программ. Должна существовать и возможность вывода отлаживаемой программы вместе с номерами операто- ров. Пользователю следует разрешить задавать уровень ото- бражения. Например, программа может быть выведена в том виде, как она написана, после макрорасширения и т. п. По- лезно также иметь средства модификации и пошаговой пере- i) Имеются в виду языки типа ассемблера и Бейсика, в которых опе- раторы нумеруются. — Прим. ред.
7.3. Системы интерактивной отладки 425 компиляции в процессе отладки. При этом система должна сохранять все заданные отладочные спецификации (точки оста- нова, режимы отображения и т. д.), с тем чтобы программисту не пришлось заново выполнять отладочные команды. Нужно, чтобы существовала и возможность вывода любых переменных и констант в символьном виде и их модификации с последую- щим продолжением выполнения программы. Тем самым вне зависимости от средств, используемых в действительности, до- стигается эффект интерпретации. В системах интерактивной отладки должны быть реализо- ваны и многие другие функции и возможности. Их описание можно найти в Сейднер [1983]. При реализации в отладочной системе описанных выше функций должен учитываться язык, на котором создана отла- живаемая программа. Многие прикладные системы и средства, предоставляемые пользователю, допускают использование раз- личных языков программирования, при работе с которыми хо- телось бы иметь единый отладочный инструмент. Команды от- ладчика, активизирующие действия и инициирующие сбор дан- ных о выполнении программы, должны быть общими для всех языков. Однако специфику языка тоже нужно принимать во внимание, чтобы процедуры, арифметические и логические операции могли кодироваться с учетом синтаксиса этого языка. Реализация этих требований оказывает влияние и на отлад- чик, и на остальное программное обеспечение. При передаче управления отладчику выполнение отлаживаемой программы временно приостанавливается. После этого отладчик должен суметь определить, на каком языке написана программа, и со- ответствующим образом осуществить настройку. Более того, он должен изменять настройку всякий раз, когда программа, написанная на одном языке, вызывает программу, написанную на другом языке. Чтобы избежать недоразумений, отладчику следует информировать об этом пользователя. Само использование настройки оказывает немалое и разно- образное влияние на диалог с отладчиком. Например, операто- ры присваивания, изменяющие значения переменных в процес- се отладки, должны быть написаны в соответствии с синтакси- сом и семантикой исходного языка программирования. Если в Коболе пользователь должен ввести команду MOVE 3.5 ТО А, то в Фортране она будет иметь вид А = 3.5. Точно так же в ус- ловных выражениях должна использоваться система обозначе- ний исходного языка. Условие «А не равно В» в программе на Коболе будет иметь вид IF A NOT EQUAL ТО В, а в про* грамме на Фортране: IF(A.NE.B). Аналогичные различия су- ществуют и для форм представления операторов, ключевых слов и т. ц.
426 Гл. 7. Другие компоненты системного программного обеспечения Система обозначений, используемая при задании определен- ных отладочных функций, соответствует языку отлаживаемой программы. Однако сами функции, по существу, не зависят от исходного языка программирования. Для выполнения операто- ров отладчик должен иметь доступ к информации, собранной языковым транслятором. Однако внутренние форматы словарей символов у различных трансляторов сильно различаются; это касается и информации по расположению операторов. Трансля- торы и ассемблеры будущего должны быть настроены на еди- ный интерфейс с системой отладки. Это можно осуществить за счет создания языковыми трансляторами не зависящей от их внутреннего представления и оформляемой в стандартной внеш- ней форме информации для отладчика. Другой способ заклю- чается в создании на этапе трансляции интерфейсных модулей отладчика, которые осуществят преобразование запрашиваемой информации к стандартному виду, не зависящему от языка, на котором написана отлаживаемая программа. Сходные вопросы возникают в процессе отладки при ото- бражении исходного кода. Опять-таки существуют две основные возможности. Языковым транслятором может быть создан ис- ходный код илй> листинг, оформленный в соответствии со стан- дартом, чтобы отладчик мог единообразно им руководствовать- ся. Или же транслятор может снабдить отладчик интерфейсным модулем, который в ответ на запросы, поступающие от отлад- чика, осуществляет управление и отображение. Важно также, чтобы отладочная система умела работать с оптимизированным кодом, обычно используемым в процессе эксплуатации программ. Однако требования, предъявляемые к обработке оптимизированных кодов, могут создать много про- блем для отладчика. Тем не менее возврат к неоптимизирован- ным видам кодов только потому, что определенные проблемы исчезнут, не оправдан. Мы рассмотрим только некоторые воз- никающие сложности. Дальнейшую же информацию можно найти в Сейднер [1983]. При многих оптимизациях осуществляется реорганизация сегментов кодов в программе. Например, из циклов могут быть удалены инвариантные выражения, отдельные циклы объеди- нены в один или цикл может быть частично развернут в бес- цикловый код. Избыточные выражения могут быть ликвидиро- ваны; в некоторых случаях возможно вообще исчезновение операторов. Блоки кодов могут быть реорганизованы в целях уничтожения лишних команд ветвления для обеспечения более эффективного выполнения программы. Примеры некоторых из этих преобразований можно увидеть в разд. 5.3.3. Все эти виды оптимизаций создают сложности для отлад- чика. Пользователь системы отладки имеет дело с исходной про- граммой в том виде, который она имела до оптимизации. Од-
7.3. Системы интерактивной отладки 427 нако реорганизация кодов изменяет порядок вычислений и мо- жет повлиять на трассировку, расположение точек останова и даже на количество операторов. Если обнаружена ошибка, мо- жет оказаться сложным определить ее истинное местонахожде- ние в исходной программе. Различного рода проблемы возникают и с памятью, отводи- мой для переменных. Когда программа транслируется, каждой переменной в основной памяти отводится своя ячейка (home location). Однако, и об этом говорилось в разд. 5.2.2, значения переменных в целях увеличения скорости доступа к ним могут время от времени храниться в регистрах. Операторы, обращаю- щиеся к этим переменным, используют значения, находящиеся в регистрах, а не в ячейках памяти. При такой оптимизации не возникает проблем с отображением значений этих перемен- ных. Однако если в процессе отладки пользователь изменит значение переменной в памяти, то оно при возобновлении вы- полнения программы, вероятно, не будет использовано. Анало- гично в случае глобальной оптимизации значение переменной может быть все время присвоено регистру. Тогда соответствую- щая ей ячейка памяти может вообще не существовать. Отладка оптимизированных кодов требует значительной по- мощи оптимизирующего транслятора. В частности, он должен сохранять информацию обо всех преобразованиях, сделанных в программе. К подобной информации можно дать доступ как программисту, так и отладчику, который, где это имеет смысл, должен использовать ее для модификации запроса, сделанного пользователем. Тем самым будет выполнена требуемая опера- ция. Например, окажется возможным достижение эффекта оста- нова программы по контрольной точке, установленной на уни- чтоженном операторе. Аналогично модифицированное значение переменной может быть сохранено как в регистре, так и в ячейке памяти. Однако некоторые более сложные оптимизации так просто не могут быть обработаны. В этих случаях отлад- чику не следует пытаться имитировать работу функции, а нуж- но проинформировать пользователя о том, что на этом уровне она не реализуема. 7.3.2. Взаимодействие с другими элементами системы Существует множество различных способов взаимодействия интерактивного отладчика с другими элементами системы. При этом единственным самым важным требованием к отладчику является его доступность в любой момент времени. Это означа- ет, что он должен казаться частью операционного окруженияг неотъемлемой составляющей самой системы. Так какаварийную ситуацию, возникшую в программе, бывает сложно или невоз- можно повторить специально, должна существовать возможность
428 Гл. 7. Другие компоненты системного программного обеспечения немедленного доступа к отладчику при обнаружении рщйбки. Таким образом, интерактивный отладчик должен тесно взаимодействовать и с такими составляющими операци- онной системы, как диалоговые подсистемы. Например, у пользователей должна существовать возмож- ность отладки программ в процессе их эксплуатации. Такого рода отладка даже важнее, чем та, что производится во время отработки системы. Это объясняется тем, что при выходе из! строя системы может остановиться зависящая от нее работа. Кроме того, многие программные ошибки, возникающие в про- цессе эксплуатации, вообще не могут быть повторены во время тестирования. Отладчик должен разрабатываться с учетом безопасности и целостности элементов системы. Нельзя допустить использова- ние отладчика кем бы то ни было в целях получения доступа к данным или кодам, иными способами не доступным. Не дол- жно существовать и возможности нарушать никоим образом целостность системы. Для входа в отладчик должны применять- ся традиционные методы авторизации, а каждое вхождение в него фиксироваться. Одним из преимуществ отладчика, по крайней мере по сравнению с дампом памяти, является возмож- ность доступа к текущей информации. В дампе же содержится лишь случайно оказавшаяся в памяти информация. Деятельность отладчика должна быть согласована и с су- ществующими компиляторами и интерпретаторами, и с теми, что будут созданы в дальнейшем (об этом говорилось в преды- дущем разделе). Предполагается и далее использовать средства отладки на существующих языках. Требования, предъявляемые к кросс-языковым отладчикам, дают возможность надеяться, что подобные средства будут применяться наряду с индивидуаль- ными языковыми отладчиками, 7.3.3. Требования, предъявляемые к интерфейсу пользователя Пользователи критически относятся к поведению интерак- тивной системы и к тому, что она собой представляет. Возмож- но, недовольство средствами отладки вызвано их новизной для пользователя. Поэтому лучше всего иметь дело с системой, Которая имеет простую организацию и позволяет вести диалог на привычном языке. В отладочной системе долж- ны быть реализованы функции, тесно связанные с задачами пользователей. Все это значительно облегчает знакомство с си- стемой и ее использование. Если диалог осуществляется с помощью дисплеев, следует использовать все заложенные в них возможности. Главное Пре- имущество таких устройств — в большом количестве отобража-
7.3. Системы интерактивной отладки 429 емой на них информации и в том, что она может быть легко и быстро изменена. Такие средства, как меню и экранные ре- дакторы, сокращают объем вводимой и запоминаемой пользо- вателем информации. Все это облегчает использование систем интерактивной отладки. С системой удобно работать, если задачи, которые нужно выполнить пользователю, заложены в организацию меню. Меню должны иметь названия, соответствующие задачам. В инструк- циях следует предусмотреть все альтернативы, доступные поль- зователю. Названия должны помочь в выборе частей меню. Основным недостатком систем с меню является отсутствие в них прямого маршрута. У пользователя дожна существовать возможность непосредственного доступа к выбранному им меню без прохода по всей иерархии. Хорошо, конечно, если в наличии имеются полноэкранные устройства и такие средства, как меню. Однако и при их от- сутствии отладочная система должна поддерживать работу ин- терактивных пользователей. Любое действие пользователя, осу- ществляемое на полноэкранном терминале, должно иметь экви- валент в линейном отладочном языке. Так, должен существовать полный функциональный эквивалент между командами и меню. Командный язык должен иметь ясный, логичный и простой синтаксис, а также как можно более походить на язык про- граммирования. Команды должны быть преимущественно про- стыми, а не составными, и содержать как можно меньше пара- метров. Во всех командах для обозначения параметров следует использовать одинаковые имена. Автоматически должна прове- ряться правильность таких характеристик параметров, как тип и диапазон значений. Для большинства параметров должны существовать значения, используемые по умолчанию. Форматы команд должны быть как можно более гибкими. В командном языке следует свести к минимуму использование скобок, кавычек и других специальных символов. Доступ к справочной информации нужно обеспечить везде, где это воз- можно. В любой хорошей интерактивной системе должна быть реа- лизована функция помощи HELP. Существенную помощь не- опытному или случайному пользователю можно оказать, выдав обыкновенный перечень возможных команд. Для опытных поль- зователей функция HELP может быть специфичной и много* уровневой. Посредством HELP на экран может быть выдай список дополнительных функций, для которых имеется поясни- тельный текст. Выбор может осуществляться по номеру или По имени дополнительной функции, а также при помощи курсора. Функция HELP должна быть доступна в любой момент процес- са отладки. Чем сложнее встретится ситуация, тем больше ве- роятность, что пользователю потребуется помощь,
Приложения Приложение А Система команд и способы адресации УУМ/ДС Система команд При описании системы команд для обозначения соответству- ющих регистров используются прописные буквы. Буквой т обо- значены адреса в оперативной, памяти, а буквой п— целые числа в диапазоне от 1 до 16. Идентификаторы регистров име- ют обозначения rl и г2. Скобки используются для обозначения содержимого области оперативной памяти или регистра. Так, выражение вида А«-(т .. т + 2) означает запись содержимого оперативной памяти с адреса т по т + 2 в регистр А, а выра- жение вида т .. т + 2<~(А)—запись содержимого регистра А в слово, начинающееся с адреса т. Буквы в столбце «примечания» имеют следующие значения: Р — привилегированная команда; X — команда, исполняемая только на модели ДС; F — команда арифметики с плавающей точкой; С — по значению результата операции (<,=,>) устанав- ливается код условия. В столбце «формат» указывается, какой командный формат УУМ/ДС должен использоваться при ассемблировании коман- ды; 3/4 означает, что может использоваться либо формат 3, либо формат 4. Для стандартной модели УУМ все команды ассемблируются в формате, описанном в разд. 1.3.1 (он совме- стим с форматом 3). Неиспользуемые поля команды (например, такие как адресное поле в команде RSUB) содержат нулевые значения. Форматы команд Формат 1 (1 байт) 8 ор Формат 2 (2 байта) 8 ор 4 4 Н г2
Приложение А. Система команд и способы адресами УУМ/ДС 431 Код Мнемокод Формат операции Результат Примечание ADD ш 3/4 18 A <- (A) + (m..m+2) ADDF m 3/4 58 F.f- (F>;+.^..n+5)^ XF ADDR rl,r2 2 90 r2 «- (r2) +(rl) X AND m 3/4 40 A <— (A) & (m. ,m+2)‘ CLEAR rl 2 B4 rl 0 X COMP m 3/4 28 (A) : .-m+2) C COMPF m 3/4 88 (F) * (mJ;m+5) 1 n : X F C COMPR rl,r2 2 AO (rl)(r2) '"4 x C DIV m 3/4 24 A <- (A) / (m..m+2) DIVF in 3/4 64 F <- (F) / (m..m+5) X P DIVR rl,r2 2 9C r2 «- (r2) / (rl) X FIX 1 C4 A <r- (F) [преобразование в целое] ' X F FLOAT 1 CO F <— (A) [преобразование в вещественное] X F HIO ,1 F4 Прекратить о$мен по каналу номер (А] X J m 3/4 3C PC 4-щ JEQ m 3/4 30 PC «-m if СС set to » JGT m 3/4 34 PC 4—m if CC set to > JLT m 3/4 38 PC m if-CC set to < JSUB m 3/4 48 L <- (PC); PC <-m LDA m 3/4 00 A <- (m.. m+2) LDB m 3/4 68 В <- (m..m+2) X LDCH m 3/4 50 A [ младший байт] 4— (m) LDF m 3/4 70 F 4- (m..m+5) X F Lt)L m 3/4 08 L <- (m.,m+2) LDS m 3/4 6C S 4— (m..m+2) X LDT m 3/4 74 T (m..m+2) X LDX m 3/4 04 X 4— (m..m+2) LPS m 3/4 DO Загрузить состояние процессора P X информацией, начинающейся адресом (разд. 6.2.1) MUL m 3/4 20 А 4— (А) # (т..т+2) MULF m 3/4 60 F 4- (F) » (т..т+5) X Р MULR rl,r2 2 98 г2 4— (r2) * (rl) X NORM 1 C8 F 4- (F) [нормализованное] 4Х F OR m 3/4 44 А*-(А) | (ш..т+2) RD m 3/4 D8 Д [ правый байг] 1—очередной байт Р данных с устройства/заданного в (т)
432 Приложения Мнемокод _ Код Формат операции Результат Примечание RMO г1,г2 RSUB SHIFTL Г1.П SHIFTR Г1,П SIO SSK и STA m STB m STCH й* STF m STI m STL M STS m STSW m STT m STX m SUB m SUBF m SUBR rl,r2 SVC n TD m 2 AC r2*~(rl) 5/4 4С PC <- (L) 2 А4 rl <— (rl); левый циклический сдвиг на и разрядов. {В ассем- блированной команде г2-п-1} 2 АЗ rl <- (rl); правый сдвиг на п разрядов с занесением в освобо- дившиеся разряды значений стар-* тих разрядов (Н). {В ассембли- рованной команде г2=п-1} 1 F0 .Активизация канала В/в номеруй); адрес канальной программы задаётся в (S) 3/4 ЁС Ключ защиты для адреса m *- (А) (разд.6,2,4) 3/4 ОС m..m+2*-(A) 3/4 78 Л..Й1+2 (В) 3/4 54 го (А) [младший байт] 3/4 80 и, ,ю+5 <- (F) 3/4 D4 Значение интервального таймера £—• (m,,m+2) (разд, 6.2.1) 3/4 14 зп« ,m+2 <- (L) 3/4 7С m..m+2<-(S) 3/4 Е8 ».,m+2 (SW) 3/4 84 Й..Ш+2 (Т) 3/4 1Q ж.В1+2<~(Х) 3/4 1С А (А) - (m.<m+2) 3/4 5С F <- (F) - (т..ш+5) 2 $4 г2 ь (г2) - (rl) 2 ВО Генерирование SVC" прерывания, {В ассемблированной команде г1-^} 3/4 ЕС Проверка устройства, заданного в (jn) X X X Р X Р X X X F Р X X р X X F X X Р С TIO TIX m TIXR rl WD Л 1 F8 Проверка канала В/В с номером (А) 3/4 2С X (X) + 1; (X) : (m.»m+2) 2 В8 X*- (X) + 1; (X) : (rl) 3/4 DC Устройство,заданное в (т) (А) [младший байт] Р X С С X с р Формат 3 (3 байта) 6 111111 12 1 ор п i X b Р е disp
Приложение А. Система команд и способы адресации УУМ/ДС 433 Формат 4 (4 байта) 6 111111 20 ор п i X b Р е address Способы адресации Ниже даются описания способов адресации, которые исполь- зуются для форматов 3 и 4. Комбинации адресных разрядов, не включенные в таблицу, рассматриваются машиной как оши- бочные. В третьем столбце таблицы буква с используется для обозначения константы в диапазоне от 0 до 4095 (или адреса памяти, о котором известно, что он лежит в этом диапазоне). Буквой т обозначен адрес или целое число больше чем 4095. Дополнительную информацию можно найти в разд. 1.3.2. Символы в столбце «примечания» имеют следующий смысл: 4 — командный формат 4; D — команда с прямой адресацией; А — ассемблер выбирает адресацию либо относительно счет- чика команд, либо относительно базы; S — совместима с командным форматом стандартной модели УУМ. Значения операндов могут находиться в диапа- зоне от 0 до 32 767 (подробнее см. в разд. 1.3.2), Обозначение Вычисление Тип Разряды-Признаки В языке целевого адресации n ixbpe ассемблера адреса ТА- Операнд Примечания , Простая 1 1 а б 0 0 ор С dxsp (TAJ D 1 1 0 0 0 X 4-ор a addr (TAJ 4 0 X 1 а 0 X 0 ср И (PC) ф disp СТА) A 1 X 0 1 0 а. ер (В] * dlsp (TA) A 1 X X 0 0 а ор сД disp + (X) СТА) D : X X 0 0 X Хор 1й.Х fcddr * (X) (TA) 4 D 1 I X 0 X а Ор аЛ (PC) + disp 4* (X) ЧТА) A 1 X 1 1 0 0 ор. а.Л (B] * disp * (X) (TA) A 0 0 0 г» в» ор a b/p/e/dU‘P (TA) D S 0 0 1 ор ж Л Ь/p/e/di^p f (X) (TA) В s Косвенная 1 0 0 0 0 а ор Фс dlsp ((TA)) D 1 0 О 0 0 X fop («fa addr (ITA1) 4 D 1 Q 0 0 I 0 ор (PCI + disp ((TA)) A 1 0 0 1 б а ор (PJ * disp I(TA)) A Непосредственная 0 1 0 0 0 а ор G dlsp tfA D с I 0 0 0 X Хор Й fcddr TA 4 P 0 1 0 0 X 0 ор м (PG) * disp TA 'A 0 1 0 X 0 а ОР ж • (B) + disp TA A
Приложение Б Таблица символов кода ASCII Код Символ Код Символ Код Символ Код Символ 00 NUL 20 SP 40 @ 60 01 SOW 21 I 41 А 61 а 02 STX 22 и 42 В 62 b оз ETX 23 # 43 С 63 с 04 EOT 24 s 44 D 64 d 05 ENQ 25 % 45 Е 65 е 06 ACK 26 & 46 F 66 f 07 BEL 27 1 47 G 67 g- 08 BS 28 ( 48 Н 68 h 09 HT" 29 ) 49 I 69 i ОА LF 2A * 4А J 6А J ОВ VT ’fcB + 4В К 6В k ОС FF 2C э 4С L 6С 1 OD CR 2D «А 4D И 6D m ОБ SO 2Б 4Е N 6Е n OF SI 2F / 4F 0 6F 0 10 DLE 30 0 50 Р 70 P 11. DC1 31 1 51 Q 71 4 12 DC2 32 2 52 R 72 r 13 DC3 33 3 53 S 73 s 14 DC4 34 4 54 Т 74 t 15 NAK 35 5 55 и 75 u 16 SYN 36 6 56 V 76 V 17 ETB 37 7 57 W 77 w 18 CAN 38 8 58 X 78 X 19 EM 39 9 59 Y 79 У 1А SUB ЗА 5А Z 7А z 1В ESC ЗВ 5В Е 7В { 1'С FS ЗС < 5С \ 7С 1 ID GS 3D =t 5D J 7D } IE RS ЗЕ > 5Е А 7Е *— IF US 3F ? 5F — 7F DEL Примечание. Коды символов приведены в шестнадцатеричном виде,
Приложение В Справочный материал по УУМ/ ДС Структура слова состояния Разряды Имя Использование поля 0 1 2—5 6-7 8-11 12-15 16-23 MODE 0 = режим пользователя; 1 = режим супервизора IDLE 0 = активен; 1 = пассивен ID Идентификатор процесса СС Код условия MASK Маска прерываний Не используются ICODE Код прерывания Прерывания Класс Адрес Тип рабочей прерывания области Код прерывания (шестнадцатеричный) I II III IV SVC 100 Код команды SVC Программное 130 Причина (см4 ниже) По таймеру 160 Нет По вводу-выводу 190 Номер канала Коды команды SVC Код Мнемоническое имя Параметры, передаваемые через регистры 0 1 2 WAIT (А) = адрес ESB для события SIGNAL . (А)= адрес ESB для события ч I/O ~ {А) = адрес канальной программы 3 4 (S) = номер канала (Т) = адрес ESB для операции обмена REQUEST (Т) = адрес имени ресурса RELEASE (Т) = адрес имени ресурса
436 Приложения Коды программных прерываний Код (шестнадцатеричный) Причина прерывания 00 01 Запрещенная команда Привилегированная команда в ре- жиме пользователя 02 03 04 10 11 12 13 Адрес вне диапазона Нарушение защиты памяти Арифметическое переполнение Отсутствует страница Отсутствует сегмент Нарушение защиты сегмента Выход за границу сегмента Формат команд канала ввода-вывода Разряды Содержимое 0-3 4—7 8-23 24-27 28-47 Код команды (см. ниже) Код устройства Количество передаваемых байтов Не используются Адрес начала массива передаваемых данных Коды команд канала ввода-вывода Код Исполняемая команда (шестнадцатеричный) 0 1 2 3—F Прекратить обмен Чтение данных Запись данных Зависит от устройства; для каждого устройства назначается индивидуально Рабочие области канала ввода-вывода Разряды Содержимое 0-2 3-5 6-8 9—В С—F Адрес текущей канальной программы Адрес ESB для текущей операции обмена Адрес очереди запросов на обмен с каналом Признаки состояния Резерв Рабочая область для канала п начинается с шестнадцатерич- ного адреса памяти 2п0.
Литература Амманн (Ammann U.) [1981] «The Zurich Implementation», in Pascal — The Language and Its Implementation, edited by D. W. Barron, John Wiley and Sons, New York. Ахо, Ульман (Aho A., Ullman J.) [1977] Principles of Compiler Desing, Addison-Wesley Publishing Co., Reading, Mass. Баас (Baase S.) [1983] VAX-11 Assembly Language Programming, Prentice-Hall, Inc., Englewood Cliffs, N. J. Баурн (Bourne S.) [1983] The UNIX System, Addison-Wesley Publishing Co., Reading, Mass. [Имеется перевод: Баурн С. Операционная система UNIX. — М.: Мир, 1986]. Браун (Brown Р.) [1974] Macro Processors and Techniques for Portable Software, John Wiley and Sons, New York. [Имеется перевод: Браун П. Макропроцессоры и мобильность программного обеспечения. — М.: Мир, 1977.] Гир (Gear С.) [1981] Computer Organization and Programming, 3rd ed., McGraw-Hill Book Co., New York. Грис (Gries D.) [1971] Compiler Construction for Digital Computers, John Wiley and Sons, New York. [Имеется перевод: Грис Д. Конструирование компи- ляторов для цифровых вычислительных машин. — М.: Мир, 1975.] Гришман (Grishman R.) [1974] Assembly Language Programming for the Control Data 6000 Series and the CYBER 70 Series, 2nd ed., Algorithmics Press, New York. Дейт (Date C.) [1981] An Introduction to Database Systems, 3rd ed., Addison-Wesley Publishing Co., Reading, Mass. [Имеется перевод первого издания: Дейт К. Введение в системы баз данных. — М.: Мир, 1980.] Дейтел (Deitel Н.) [1984] An Introduction to Operating Systems, rev. ed., Addison-Wesley Publishing Co., Reading, Mass. [Имеется перевод: Дейтел Г. Введение в операционные системы. — М.: Мир, 1987.] Деннинг (Denning D.) [1983] Cryptography and Data Security, Addison-Wesley Publishing Co., Reading, Mass. Джонсон (Johnson S.) [1975] «YACC —Yet Another Compiler-Compiler», Computing Science Technical Report 32, Bell Laboratories, Murray Hill, N. J. [1980] «Language Development Tools on the UNIX System», Computer 13: 16—21, August. Донован (Donovan J.) [1972] Systems Programming, McGraw-Hill Book Co., New York. [Имеется перевод: Донован Дж. Системное программирование. — Мд Мир, Йенсен, Вирт (Jensen К., Wirth N.) [1974] PASCAL User Manual and Report, Springer-Verlag, New [Имеется перевод: Йенсен К., Вирт Н. Паскаль. Руководство для зователя и описание языка. — М.: Финансы и статистика, 1982.] Кервдган, Пдаугер (Kernigan В., Plauger Р.) [1976] Software Tools, Addison-Wesley Publishing Co.f Reading, Mass. 1975.] York. ПОЛЬ-
438 Литература Кнут (Knuth D.) [1973а] The Art of Computer Programming, Vol. I: Fundamental Algo- rithms, 2nd ed., Addison-Wesley Publishing Co., Reading, Mass. [Имеется перевод: Кнут Д. Искусство программирования для ЭВМ. Т. 1: Основ- ные алгоритмы. — М.: Мир, 1976.] [19736] The Art of Computer Programming, Vol. 3: Sorting and Sear- ching, Addison-Wesley Publishing Co., Reading, Mass. [Имеется перевод: Кнут Д. Искусство программирования для ЭВМ. Т. 3: Алгоритмы сор-» тировки и поиска. — М.: Мир, 1978.] Коул (Cole А.) [1981] Macro Processors, Cambridge University Press, New York. Кэмпбел-Келли (Campbell-Kelley M.) [1973] An Introduction to Macros, MacDonald. [Имеется перевод: Кэмп- бел-Келли M. Введение в макросы. — М.: Советское радио, 1978.] Леек (Lesk М.) [1975] «Lex — A Lexical Analyzer Generator», Computing Science Techni- cal Report 39, Bell Laboratories, Murray Hill, N. J. Лорин, Дейтел (Lorin H., Deitel M.) [1981] Operating Systems, Addison-Wesley Publishing Co., Reading, Mass. Лумис (Loomis M.) [1983] Data Management and File Processing, Prentice-Hall, Inc., Engle- wood Cliffs, N. J. Льюис, Розенкранц, Стирнз (Lewis P., Rosenkrantz D., Stearns R.) [1976] Compiler Desing Theory, Addison-Wesley Publishing Co., Reading* Mass. [Имеется перевод: Льюис Ф., Розенкранц Д., Стирнз Р. Теоре- тические основы проектирования компиляторов. — М.: Мир, 1980.] Мартин (Martin J.) [1977] Computer Data-Base Organization, 2nd ed., Prentice-Hall, Inc.* Englewood Cliffs. [Имеется перевод: Мартин Дж. Организация баз данных в вычислительных системах. — М.: Мир, 1980.] Мейровитц, ван Дам (Meyrowitz N., van Dam А.) [1982а] «Interactive Editing Systems: Part I», ACM Computing Surveys 14 : 321—-352, September. [19826] «Interactive Editing Systems: Part II», ACM Computing Sur- veys 14: 353—416, September Мэдник, Донован (Madnick S., Donovan J.) [1974] Operating Systems, McGraw-Hill Book Co., New York. [Имеется перевод: Мэдник С., Донован Дж. Операционные системьь—М.: Мир, 1978.] Нори, Амман, Йенсен, Нагели, Якоби (Nori К., Amman U., Jensen К., Nageli Н., Jacobi С.) [1981] «Pascal-Р Implementation Notes», in Pascal—The Language and Its Implementation, edited by D. W. Barron, John Wiley and Sons, New York. Овергаард (Overgaard M.) [1980] «UCSD Pascal: A Portable Software Environment for Small Computers», Proceedings, 1980 National Computer Conference 49: 747« Питерсон, Силбершатц (Peterson J., Silberschatz A.) [1983] Operating Systems Concepts, Addison-Wesley Publishing Co., Reading, Mass. Пфлигер (Pfleeger C.) [1982] Machine Organization: An Introduction to the Structure and Programming of Computer Systems, John Wiley & Sons, New York. Ритчи, Томпсон (Ritchie D., Thompson K.) [1978] «The UNIX Time-Sharing Systems», Bell System Technical Jour- nal 57, no. 6 pt. 2: 1905—1930, July —August. Сасса (Sassa M.) [1979] «А Pattern Matching Macro Processor», Software: Practice ano Experience, pp. 439—456, Jurte.
Литература 439 Сейднер, Тиндолл (Seidner R., Tindall N.) [1983] «Interactive Debug Requirements», Proceedings of4 the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on High-Level Debugging, March 1983, напечатана в SOFTWARE ENGINEERING Notes and SIGPLAN Notes, August 1983, pp. 9—22. Сирайт, Маккиннон (Seawright L., MacKinnon R.) [1979] «VM/370 — A Study of Multiplicity and Usefulness», IBM Systems Journal 18, no. 1 :4—17. Стендиш (Standish T.) [1980] Data Structure Techniquest Addison-Wesley Publishing Co., Read* ing, Mass. Страбл (Struble G.) [1983] Assembler Language Programming: The IBM System 370, 3rd ed., Addison-Wesley Publishing Co., Reading, Mass. Танненбаум (Tannenbaum A.) [1984] Structured Computer Organization, Prentice-Hall* Inc., Englewood Cliffs, N. J. Трембли, Соренсон (Tremblay J., Sorenson P.) [1984] An Introduction to Data Structures with Applications, 2nd ed., McGraw-Hill Book Co., New York, Уидерхолд (Wiederhold G.) [1983] Database Design, 2nd ed., McGraw-Hill Book Co., New York. Ульман (Ullman J.) [1980] Principles of Database Systems, Computer Science Press, Princeton. Фернандес, Саммерс, Вуд (Fernandez E., Summers R., Wood C.) [1981] Database Security and Integrity, Addison-Wesley Publishing Co., Reading, Mass. Хантер (Hunter R.) [1981] The Design and Construction of Compilers, John Wiley and Sons, New York. Холт, Грэхем, Лазовска, Скотт (Holt R., Graham G., Lazowska E., Scott M.) [1978] Structured Concurent Programming with Operating Systems Ap- plications, Addison-Wesley Publishing Co., Reading, Mass. Хопгуд (Hopgood F.) [1969] Compiling Techniques, MacDonald/American Elsevier, London [Имеется перевод: Хопгуд Ф. Методы компиляции. — М.: Мир, 1972.] CDC (Control Data Corporation) 1981а] CYBER 170 Computer Systems Hardware Reference Manual 1982a' COMPASS Version 3 Reference Manual. 19826] CYBER Loader Version 1 Reference Manual. 1979] NOS International Maintenance Specification* [19816] NOS Reference Manual. DEC (Digital Equipment Corporation) [198Г ” ........ ........... 1978] 1979] VAX-11 MACRO Language Reference Manual. 1982 ...... " ......... VAX Architecture Handbook. VAX-11 Linker Reference Manual. VAX Software Handbook. IBM" (International Business Machines Corporation) IBM Sy stem!370 Principles of Operation. OS/VS DOS/VS E VM/370 Assembler Language. OS/VS VM/370 Assembler PLM. OS/VS VM/370 Assembler Programmer's Guide* OS/VS Linkage Editor and Loader. 1983' 1979' 1974 1982 1978] 1972a] OS/VS Linkage Editor Logic. 19726] FORTRAN IV (H) Compiler Program Logic Manual. SofTech (SofTech Microsystems) [1980] UCSD Pascal Users Manual. [1983] P-Systems Internal Architecture Reference Manual.
Предметный указатель Абсолютная программа 58, 97, 98, 126, 177 Абсолютное — выражение 73, 90, 135 — имя 73, 79, 90 Автоматический библиотечный поиск 145, 146, 157, 160, 165, 171, 175 Автоматическое распределение памяти 274, 289, 301 Автономная программа 380—382 Авторизация 374—376, 428 Адресация — относительно базы ------ассемблирование 50, 54—56, 66, 67 ------ компиляция 274 ------перемещение 61 ------УУМ 19, 433 ------System/370, 26, 103—105 ------VAX 31, 32 — относительно счетчика команд ---------ассемблирование 50, 54—56, 79, 121, 133 • ------перемещение 61, 138 ---------УУМ 19, 433 ---------VAX 108, 109 Адресная константа 104, 107 Адресное пространство 359, 360 Алгол-68 293 АПЛ 295 Арифметическое выражение ---генерация кода 254—259, 309, 310 ---разбор 236, 239-245, 247—250, 308, 309 ---синтаксис 230, 244, 308, 309 Ассемблер — алгоритмы 46—49 — второй просмотр 45, 47—49 — диагностика ошибок 48, 54, 74 — многопросмотровый 99—103 — однопросмотровый 93—99, 120, 121 — первый просмотр 45, 46, 67, 72, 77, 91— 93 — промежуточный файл 48, 49, 92, 97, 117 — структуры данных 45—48 Ассоциативная память 359 База данных ---администратор 404, 408 •--организация 400 —408 Базовый регистр --адресация 55, 66, 67, 117 ---перемещение 61, 348, 350 ---УУМ 17 ---System/370 23, 69, 103—105, 108 Библиотека 145, 146, 159, 160, 165, 175 Блокирование записей 363—365, 393, 397 Блокировка процесса 393 Блок состояния ---процесса 329, 330 <--события 331, 332, 334—342, 398, 435, 436 Блок-схема 268, 310 Блочно-структурированный язык 270, 287-^ 291 БНФ 225 Буфер 363—365, 347 Буферизация 363—365, 397 — двойная 365 Взаимоблокировка процессов 374, 398 Виртуальная — машина 318, 377, 380—383, 393—395 — память 28, 29, 172, 351—361, 382, 392, 393, 397, 421 Виртуальное адресное пространство 351, 359 Виртуальный — адрес 353—357 — ресурс 351 Вложенные — блоки 287—289, 295, 311 — макроопределения 184, 185, 187 Внешние имена ----динамическое связывание 161—163 ----исходная программа 83, 107, 133—136 •---объектная программа 83—88, 134—136 ----оверлейная программа 152, 166 ----System/370 107 — ссылки •---автоматическое разрешение 145, 146, 158—160 ----ассемблирование 83—88, 120, 121 ----динамическое связывание 163 ---- компиляция 259 ----связывание 88, 89, 133—138, 158, 174—« 177 ----VAX ПО, 171 Внешняя память 351, 357 Восходящий разбор 235—242, 308 Время ответа 367 Время прохождения задания 367—369, 397 Выравнивание 22, 29, 105, 106, ПО Выражение (в языке ассемблера) 72—75, 86, 90, 119, 120, 133 Вычисление смещения 54—56, 79, 104 Генерация кода 223—225, 251—259, 278—280, 298, 309, 310 Грамматика — БНФ 225, 226 — генерация кода 251 — компилятор компиляторов 297—299, 305—» 308 — лексический анализ 231—235 — Паскаль 225 — синтаксический анализ 235—250, 310 Граничные регистры 346, 347, 396
Предметный указатель 441 Дерево грамматического разбора 226—231, 235—242, 247, 252-259, 298, 308, 309 Динамическое — связывание 123, 160—163, 177 — распределение памяти »—-----в компилируемой программе 270-* 276, 280 —------в операционной системе 346, 347, 350, 388 Директива ассемблера 39, 43, 45, 71, 120 Диспетчер — алгоритм 330, 331 — назначение 329 Диспетчеризация — круговая 330 — определение 328—329 — приоритетная 330—332, 367, 391, 393, 396 Дисплей (в блочно-структурированных языках) 289—291, 311 Зависимость данных 400, 401 Загрузка 122, 123, 139, 142 Загрузка — выполнение 95, 121 Загрузочный модуль 157, 165, 166, 168 Загрузчик — абсолютный 123—126, 164, 174, 176 — алгоритмы 122—126, 139—144 — диагностические сообщения 145, 174 — директивы 146—149, 151, 152, 159, 175 — перемещающий 127—132, 157, 158 — раскручивающий 163—164, 177 — структуры данных 139—144 Задание 328, 343-346, 365—369, 390, 391 Запись (данных) 362—365 Запись-заголовок 43, 124, 125, 140, 141 Запись-конец 43, 44, 61, 124, 125, 141, 142 Запись-модификатор — обработка 142, 143 — перемещение 59, 60, 67, 75, 88, 89, 127— 129, 135, 136, 158 — связывание 87—91, 121 — формат 59, 60, 87, 88, 117, 118 Запись-определение 87, 88, 140, 141, 145 Запись-ссылка 87, 88, 143 Запрос — ввода-вывода 334—342, 357, 362—365, 396 — ресурса 371—374, 385, 397—398, 435 Зарезервированное слово (идентификатор) 235, 300 Защита — данных 407 — памяти 28, 346, 347, 377, 396, 397, 436 — средства 374—377, 382, 407, 428 Значение по умолчанию 199, 220 Зонный десятичный формат 23, 31 Идентификатор процесса 347, 435 Идентификация пользователя 375 Иерархические структуры 377—381 Иерархия — прозрачная 379 — строгая 379 Избыточность данных 400—402, 404 Инвариант цикла 281—284, 305, 311, 426 Индексная адресация ----ассемблер 39, 56, 86 ---- компиляция 278 ----УУМ 16, 19 •---CYBER 36 . .. ----System/370 26 __ __ VAX 32 . ,. Индексный регистр 15, 23, 26, 32, 34 Интерактивный режим 315, 394 Интервальный таймер 324/ 328, 330, 396 Интерпретатор 294—297/386/425 Интерфейс — оператора 316 — пользователя 316, 317 Исполнительный адрес 20 Канал ввода-вывода ----УУМ 21 ----System/370 28 Канальная — команда 333—337, 362, 435, 436 — программа 333—337, 362 Каталог 362 Квант времени 328—332, 339, 368, 370, 373, 396 Ключ защиты памяти 347, 396 Ключевое слово 223, 231, 232, 234, 235 Ключевые макропараметры 198—201, 209, 220 Код — лексемы 232—234, 309 — символов ASCII 434 — условия 15—18, 23, 27, 30, 325, 430 Компилятор — диагностика ошибок 238, 243, 245, 3)0 — компиляторов 297—299, 305—308 — на Р-код 296, 297, 302, 303 — однопросмотровый 223, 224, 292, 293, 299—303 — Паскаль 299—303 — просмотры 224, 292-294 — Фортран 299, 303—305 Конкатенация 189, 190, 214, 219 Контекстное переключение 322, 323, 325, 346, 350 Контроль доступа 374—377 Контрольная точка 387, 394, 424, 427 Конфигурация ввода-вывода 33 Координатный манипулятор 414 Корневой сегмент 91, 92, 120, 150—153, 168 Косвенная адресация — — ассемблирование 57 ----в исходной программе 50 ---- компиляция 275 ----УУМ 19, 433 ----CYBER 36 __ __ VAX 32 Курсор 414—416, 421 Левая рекурсия 244, 245 Лексема 206, 208, 223, 226 Лексический анализ 223, 224, 231—235, 294, 298, 309, 310 Линейный участок 267, 268, 281, 304, 310 Линия связи 376, 383, 384 Литерал 62—68, 118, 121 Литеральный пул 65—67, 118 Локальность ссылок 359, 360, 397 Макровызов 179, 181 (см. Макроинициали- зация) Макроинициализация 181—183, 198—204, 212—220 Макроинструкция — аргументы 181, 182, 185—187, 193, 198— 201, 217 — метки 183, 190-192, 212, 213 — определение 178—183, 193—195, 210, 215— 218 — параметры 180, 181, 185—187, 193, 198— 201, 205, 212, 214—220 — прототип 181, 185, 186, 199 — расширение 178, 181—183, 192—198, 212 — тело 181—187, 203, 217 Макропроцессор — алгоритмы 183—185, 188 — встроенный 206—209
442 Предметный указатель диагностика ошибок 220, 221 • — общего назначения 204—206, 215—218 строка за строкой 207, 221 — структуры данных 183—187 Маска перемещений 131, 132, 172, 173, 174 Массив — распределение памяти 276—279 — ссылка на элементы 277—280, 286, 296, 302, 311 Матрица — доступа 374, 375 — предшествования 237—242, 309 Менеджер оверлея 153—155, 175 Меню 316, 387, 416, 429 Метка 44, 46, 49 Метод — доступа 362 «— операторного предшествования 236—242, 251, 309 — рекурсивного спуска 242—250, 300, 309 Модель данных 405 Монитор виртуальной машины 380—383, 394, 395 Мультипрограммирование 52, 315, 343, 358, 365—367, 380, 395 Мультипрограммная система 315, 318, 320, 328, 343, 365, 366, 368, 396, 397 Мультипроцессорная система 33, 315, 383— 386, 390 — — главный — подчиненный 384, 385 ----симметричная 384—386 — слабо связанная 383—385 Независимость данных 403, 404 Неоднозначная грамматика 230, 308 Непосредственная адресация — — ассемблирование 56, 57, 65 ----в исходной программе 50, 118 ----компиляция 310 — — УУМ 19, 433 ----CYBER 36 ----System/370 26 ----VAX 32, 108 Неразрешенные внешние ссылки 145, 149 Нетерминальный символ 226, 239—247, 251, 252 Нисходящий разбор 235, 242—250 Область инициализации 273—276, 289—291, 301, 311 Оболочка 388 Обратная трассировка 424 Обслуживание ввода-вывода 332—343, 378, 379, 396 Общая область 175 Общее подвыражение 281—284, 305, 311, 426 Объектная программа --__для однопросмотрового ассемблера 93__gg ----загрузка 122—139, 174 »— — компиляция 251, 259 >— — программные блоки 75—81 — — управляющая секция 85—91 — — формат 43, 44 *---CYBER 172—174 •---System/370 165—168 ----VAX 168-172. Оверлейная • — программа 91—93, 149—156, 166—168, 172, 173, 175, 176 ► — структура 149—156, 166—168, 175 Однопрограммная система 314, 365, 368, 397 Окно 419, 420 Оператор — конкатенации 190, 214, 219 — присваивания --генерация кода 254—259, 268, 269, 309 310 ---’разбор 239—242, 247—250, 309 ---синтаксис 223—230 Операционная система ---интерфейс пользователя 312, 313, 316, 317, 378, 387, 388, 394 ---классификация 314—316 — — структура 377—386 ---цели 315, 316 Операционное окружение 313, 314, 317-* 319, 427 Операция ввода-вывода 317, 318, 321, 388, 390, 393, 396 Описание — данных 404—408 — отображения данных 403, 404, 407, 410 Описатель массива 280 Оптимизация — в компиляторе FORTRAN Н 303—305 — машинно-зависимая 263, 266—270 — машинно-независимая 280—286 — цикла 281—284, 305 Освобождение ресурса 372—374, 385, 397, 398 Отладка 295, 387, 394, 423—429 Относительная адресация 17—21, 128, 129, 138 Относительное выражение 73, 74, 90 Относительный терм 73, 74, 90 Отношение предшествования 237—242 Очередь — входных заданий 366 — запросов к каналу 338, 339, 396 Ошибки ввода-вывода 317, 336, 339 Пароль 375, 389 Паскаль 234, 271, 275, 287, 292, 296, 299—303 Пассивные байты 105, 106 Переменная периода макрогенерации 193— 198, 210—213, 216—218 Перемещаемая программа 52, 58, 117. 121, 126, 129 Перемещение — во время исполнения 347—350 — определение 58, 122 — с помощью записи-модификатора 59, 60. 88-91, 127, 129, 135—139 — с помощью маски перемещения 130—132 — связывающим загрузчиком 157 — CYBER 172, 173 — System/370 104 - VAX 171 Переносимость 296, 302, 386 Периферийный процессор 33, 37. 384, 390 ПЗУ 164 Планирование — заданий 365—369 ---цели 367—369 — процессов 328—332, 339, 367, 379, 382, 393 Планировщик промежуточного уровня 367, 393 Планшетка 414 ПЛ/1 235, 274, 275, 279, 280, 292 Подсхема 406—411 Позиционная нотация 186, 219 Позиционный параметр 198, 199, 209, 220 Правила вывода 225—230 Прерывания — вложенные 327, 338 — запрещенные 326, 395, 398 — классы 321, 324, 326, 395 — маркированные 326, 327
Предметный указатель 443 — обработка 320—327, 356—358, 379, 397 — обработчик первого уровня 378, 379 — определение 318, 320 -— по вводу-выводу 321, 324—327, 333, 338— 342, 397, 435 — по таймеру 324, 327, 396—398, 435 — приоритет 326, 395—397 — программное 321—326, 334; 382, 435, 436 — SVC 321—326, 397, 435 Привилегированная команда 319, 321, 382, 430—432, 435, 436 Пробуксовка 359, 366, 367, 397 Программа — генерации кода 251—261, 292, 298, 310 — обработки прерываний .-----ввода-вывода 338 ------местонахождение 379 ------от таймера 328—330 ------принципы построения 320—327 ------SVC 331, 332, 338 — связывания 122, 168—172 — управления файлами 362—365, 397 Программно-аппаратные средства 314 Программный блок 75—82, 107, 113, 114, 119, 120 Пролог 274, 311 Промежуточная форма представления про- граммы — интерпретация 294, 295 — оптимизация кода 251, 263—266, 281 Промежуточный результат 264—269, 281, 310 Пропускная способность 367—369 Простая адресация 19, 433 Проталкивание 112 Процесс — активный 328—332 — блокированный 328—332, 342, 357, 372, 374 396 — обработка 328—332, 338—342, 356—358, 369—374, 389, 392, 393 — состояние готовности 328—332, 342, 357, 372, 396 Прямая адресация ----перемещение 61, 129—132, 138, 174 ----УУМ 19, 433 ----CYBER 36 ----System/370 26 Псевдомашина 296, 297, 302, 303, 386 Рабочая — ооласть канала 336—338 ------прерывания 324—327, 330, 357, 358 — переменная 258, 259, 267—269 Рабочее множество процесса 358—360, 393 Раздел — переменный 343, 345, 346 — перемещаемый 347—350, 397 — фиксированный 343—345, 396 Размещение — наиболее подходящее 346 — первое подходящее 346 — страниц по запросу 351—361, 366, 392, 397 Разряд перемещения 131, 132, 172 Расположение массива — по столбцам 277, 278 — по строкам 277, 311 Распределение — памяти 270—276 — регистров 266, 267, 301, 304, 305, 310 Расширенная машина 313, 317—319, 377 Расширенный командный формат ------------непосредственная адресация 56 •----- определение 18 ------перемещение 61, 129 — ~ — прямая адресация 50, 54 -------связывание 86, 120, 133, 138 Реальная память 351—356, 360, 361 Регистр перемещения 349, 350, 390 Редактор связей 122, 156—160, 165—168, 176, 2оЗ Режим — пользователя 318, 319, 325, 377, 382, 435, 436 — супервизора 318, 319, 325, 347, 377, 381, 382, 435 Рекурсивная — грамматика 226, 245 — макрогенерация 179, 203, 204, 221 Рекурсивный вызов 204, 270—274, 290, 291 Самокомпилятор 300 Свертывание 367, 391 Связывание 82—91, 122, 123, 132—139, 142-=* 144, 152, 153, 156, 157, 168—172 Связывающий загрузчик 123, 139—144, 156, 157, 168, 175 Сегмент 91—93, 150—156, 166—168, 176 Сегментация 361 Семантика 224, 225, 263, 425 Семантическая программа 251, 298, 307, 308, 417, 418 Синтаксис 114, 223—227, 263, 425 Синтаксически определяемые команды 114-116 Синтаксический — анализ 224, 235—250, 294, 310 — анализатор 223, 298, 299 Синхронизация 374, 389, 393 Система — отладки ---интерфейс пользователя 424, 425, 428, 429 ------транслятора 425—427 ---командный язык 429 •--отображение 424—426 — — функции 424—427 — пакетной обработки 315, 319, 368, 369, 391, 396 — разделения времени 315, 319, 367, 368, 384, 391, 422 — реального времени 315, 319, 391, 396 — управления базой данных 399—411 Системное программное обеспечение 12, 13 Сканер 223, 231—235, 292, 298, 299, 305— 309 Слово состояния 15, 324, 325, 358, 435 Смещение 26, 32, 61, 103, 109, 274 Снобол 295 Событие 328—332, 334, 338, 342, 396, 435 Совместимость снизу вверх 14, 20, 22, 28, 29, 30, 33 Состояние процесса 328—330 Спецификатор — лексемы 232—234, 252—258 — узла 255—258 Список авторизаций 375 Ссылка вперед 93—99, 120, 121, 259, 293 Ссылочный номер 143, 144, 174 Статическое распределение памяти 270, 271 Страница 351—361, 366, 379, 392, 397 Страничное прерывание 355, 359, 379, 392, 436 Страничный кадр 351—358, 392 Стратегия — выталкивания страниц 358, 397 — планирования заданий 369’ Структурированная переменная 270^ 276~< 280 Супервизор оверлея 156 Счетчик — адреса 45, 46 — команд
444 Предметный указатель •---при адресации 54, 55, 79, 109 •---при переключении контекста 322—324, 329 •---при перемещении 60, 138 ----УУМ 15 ---System/370 23 ----VAX 30, 109 — позиций 111, 112, 114 г- размещений 112—114 »— сЛов 112—114 Схема 405—411 Файл — закрытие 363 — защита 374—377 — обработка 362—365 — открытие 363 Файловая информационная таблица 361 Флаг состояния канала 336—339 Формат данных с плавающей точкой 17 18, 24, 25, 30, 31, 34, 35 Фортран 234, 235, 270, 285, 292 Фрагментация памяти 347, 359 Таблица — аргументов 185—187, 203, 204, 221 — базовых регистров 104 — внешних имен 139—144 * — загрузки 141, 142, 149 имен 45, 54, 55, 75—79, 86, 91, 95—97, 99—103, 119 •---макросов 185—187, 207, 219 — кодов операций 45—49, 52, НО, 115, 116, 207 — литералов 67 — макроопределений 185—187, 195, 196, 198, 219 — отображения страниц 352, 354—359, 392 — перекрестных ссылок 120, 121 — сегментов 153—156, 166, 175 Текстовый редактор ----интерфейс пользователя 413—417 ----отображение 412, 417—421 ---просмотр 412, 413, 417, 418 ----редактирование 412, 418—421 •---структура 417—423 >---устройства ввода-вывода 413—415, 421, 422 »— — фильтрация 412, 413, 417—419 Тело программы 43, 44, 80—82, 97, 98, 124, 125, 131, 132 Терминальный символ 226 Трассировка 424, 427 Упакованный десятичный формат 23, 27, 31 Управление — памятью 320, 343—350, 378 — ресурсом 313, 317—319, 369-374, 384—386 Управляемое распределение памяти 275 Управляющая секция — ассемблирование 83—87, 119, 120 »— Определение 75, 82 — связывание и загрузка 88. 89, 132—142 — CYBER 113, 114 г— System/370 107, 108, 165 — VAX 110 Управляющий регистр 23, 28, 30 Уровень — иерархии 377—380 — мультипрограммирования 343, 365—367 Условие периода макрогенерации 193—196 Условная макрогенерация 178, 192—198, 210-213, 216 Условное ассемблирование 192, 210—213 ! Устройство речевого ввода 415 УУМ — ввод-вывод 16, 17, 21 — г.зналы 436 — коды SVC 435 — память 15, 17 — прерывания 435, 436 — регистры 15, 17 — система команд 16, 21, 430—432 — слово состояния 435 V- спсобы адресации 16, 19—21, 433 — форматы данные 1/, 18 форматы команд 15. 18, 430 -433 Хеш-таблица 47, 139, 289 Целевой адрес — при вычислении смешения 54, 55 — при перемещении 58, 61, 138 — УУМ 16, 19—21 — CYBER 36 — System/370 26 - VAX 31. 32 Цикл периода макрогенерации 196—198 Четверка 264—269. 281—286, 294, 304, 310, 311 Шифровка данных 376, 377 Экранная сенсорная панель 414 Эпилог 274, 311 Ядро — безопасности 377 — операционной системы 378, 379* 381 Язык — запросов 408—410 — манипулирования данными 408, 409 — описания данных 407 ---- подсхем 407 ----схем 408 — — физических данных 408 — управления заданиями 316 BASE 30, 55, 56, 79, 104, 117 BYTE 39. 43. 45 CSECT 83 CYBER — ассемблер 111—116 — ввод-вывод 37 — загрузчик 172—174 — операционная система NOS 390, 391 — память 34 — Паскаль-компилятор 299—301 — регистры 34 — система команд 36, 37 — способы адресации 36 — форматы данных 35, 36 — форматы команд 35, 36 END 39, 43 EQU 68-72. 99 EXTDEF 83, 86—83, 120 EXTREF 83, 86-88, ПО. 120, 175, 253 GETA 257, 258. 267^
Предметный указатель' 44S JF 193-196, 201, 212, 213, 219 — память 22 — регистры 23 — связывающий загрузчик 165—168 LEX 299, 305—308 LPS 322-324, 330, 331, 338, 346, 357 LTORG 65, 66, 79 — система команд 27, 28 — способы адресации 26, 27 — форматы данных 23—25 — форматы команд 25, 26 — Фортран-компилятор 303—305 MACRO 179, 187 MASK 325—327, 358, 435 MEND 179, 187 ТЮ 338 UCSD Pascal компилятор 297, 299, 302, 303 NOS 390, 391 UCSD Pascal система 386—388 UNIX 299, 305, 388-390 ORG 70—72, 121 USE 77 PSW 23, 28 VAX — ассемблер 108—110 — ввод-вывод 33 RESB 39, 43 RESW 39, 43, 45 — макропроцессор 213—215 — операционная система VMS 391—393 — память 29 SET 193, 195 SIGNAL 331, 332, 339, 342, 393, 435 S1O 333, 338 SSK 347 START 39, 43, 45, 83, 259 STI 324, 330 SVC 21, 27, 318, 321, 329-331, 334-342, 371—373, 435 System/360 22 System/370 22 — ассемблер 103—108 — ввод-вывод 28 — макропроцессор 209—213 — операционная система VM/370 393, 394 — программа связывания 168—172 — регистры 29, 30 — система команд 32 — спецификация операндов 31, 108, 109 — способы адресации 31, 32, 108—110 — форматы данных 30, 31 — форматы команд 31 VAX/VMS 391-393 VM/370 393-395 WHILE 196, 198, 212, 213, 220 WORD 39, 43, 45 YACC 299, 305-308
Оглавление От редактора перевода ............................................... 5 Предисловие.......................................................... 7 Глава 1. Основные понятия..........................................11 1.1. Введение ..................................................П 1.2. Системное программное обеспечение и структура ЭВМ . . 12 1.3. Упрощенная учебная машина (УУМ)......................14 1.3.1. Структура УУМ..................................15 1.3.2. Структура УУМ/ДС ...................................17 1.4. Структура System/370 ................................... 21 1.5. Структура ЭВМ VAX.........................................28 1.6. Структура ЭВМ CYBER.......................................33 Глава 2. Ассемблеры .................................................38 2.1. Основные функции ассемблера...............................39 2.1.1. Простой ассемблер для УУМ...........................41 2.1.2. Таблицы и алгоритмы ассемблера.................45 2.2. Машинно-зависимые характеристики ассемблера..........49 2.2.1. Форматы команд и способы адресации.............52 2.2.2. Перемещение программ...........................57 2.3. Машинно-независимые характеристики ассемблера .... 62 2.3.1. Литералы ...........................................62 2.3.2. Средства определения имен...........................68 2.3.3. Выражения ..........................................72 2.3.4. Программные блоки...................................75 2.3.5. Управляющие секции и связывание программ .... 82 2.4. Варианты построения ассемблеров.........................91 2.4.1. Двухпросмотровый ассемблер с оверлейной структурой 91 2.4.2. Однопросмотровые ассемблеры.........................93 2.4.3. Многопросмотровые ассемблеры........................99 2.5. Примеры реализации.....................................103 2.5.1. Ассемблер System/370 ............................. 103 2.5.2 Ассемблер ЭВМ VAX................................108 2.5.3. Ассемблер ЭВМ CYBER...............................Ill Упражнения ................................................ 116 Глава 3. Загрузчики и программы связывания..........................122 3.1. Основные функции загрузчика .............................123 3.2. Машинно-зависимые свойства загрузчиков...................126 3.2.1. Перемещение........................................127 3.2.2 Связывание программ................................ 132 3.2.3. Таблицы и алгоритмы связывающего загрузчика . .139 3.3. Машинно-независимые свойства загрузчиков.................144 3.3.1. Автоматический поиск в библиотеках.................145 3.3.2. Управление процессом загрузки......................146 3.3.3. Оверлейная структура программ......................149
Оглавление 3.4. Варианты построения загрузчиков . ....................156 3.4.1. Редакторы связей ................................156 3.4.2. Динамическое связывание..........................160 3.4.3. Раскручивающие загрузчики...................... 163 3.5. Примеры реализации................................. 164 3.5.1. Редактор связей System/370 ..................... 165 3.5.2. Программа связывания ЭВМ VAX.....................168 3.5.3. Загрузчик ЭВМ CYBER............................. 172 Упражнения ................................................174 Глава 4. Макропроцессоры.........................................178 4.1. Основные функции макропроцессоров.....................179 4.1.1. Макроопределения и микрсрасширения...............179 4.1.2. Макропроцессор. Таблицы и логика.................183 4.2. Машинно-независимые особенности макропроцессора . . . 189 4.2.1. Конкатенация макропараметров.....................189 4.2.2. Генерация уникальных меток.......................190 4.2.3. Условные макрорасширения.........................192 4.2.4. Ключевые макропараметры.......................... . 198 4.3. Варианты построения макропроцессоров..................201 4.3.1. Рекурсивная макрогенерация ......................203 4.3.2. Макропроцессоры общего назначения................204 4.3.3. Макропроцессоры, встроенные в трансляторы .... 206 4.4. Примеры реализации.................................. 209 4.4.1. Макропроцессор System/370 ...................... 209 4.4.2. Макропроцессор системы VAX.......................213 4.4.3. Макропроцессор общего назначения РМ..............215 Упражнения ................................................218 Глава 5. Компиляторы ............................................222 5.1. Основные функции компилятора..........................222 5.1.1. Грамматики.......................................224 5.1.2. Лексический анализ ..............................231 5.1.3. Синтаксический анализ............................235 5.1.4. Генерация кода...................................251 5.2. Машинно-зависимые особенности компиляторов............263 5.2.1. Промежуточная форма представления программы . . 264 5.2.2. Машинно-зависимая оптимизация кода...............266 5.3. Машинно-независимые особенности компиляторов..........270 5.3.1. Распределение памяти........................... 270 5.3.2. Структурированные переменные.....................276 5.3.3. Машинно-независимая оптимизация кода ...... 280 5.3.4. Блочно-структурированные языки...................287 5.4. Варианты построения компиляторов......................292 5.4.1. Разбиение на отдельные просмотры.................292 5.4.2. Интерпретаторы ..................................294 5.4.3. Компиляторы на Р-код.............................296 5.4.4. Компиляторы компиляторов ........................297 5.5. Примеры реализации....................................299 5.5.1. Компилятор ETH Pascal............................299 5.5.2. Компилятор UCSD Pascal...........................302 5.5.3. Компилятор Fortran Н фирмы IBM...................303 5.5.4. Компилятор компиляторов YACC.....................305 Упражнения ................................................308 Глава 6. Операционные системы....................................312 6.1. Основные функции операционных систем ........ 312 6.1.1. Типы операционных систем 314
448 Оглавление 6.1.2. Интерфейс пользователя.............................316 6.1.3. Операционное окружение.............................317 6.2. Машинно-зависимые свойства операционных систем .... 319 6.2.1. Обработка прерываний...............................320 6.2.2. Планирование процессов.............................328 6.2.3. Обслуживание ввода-вывода........................ 332 6.2.4. Управление реальной памятью........................343 6.2.5. Управление виртуальной памятью ....................351 6.3. Машинно-независимые свойства операционных систем . .361 6.3.1. Работа с файлами...................................362 6.3.2. Планирование заданий ..............................365 6.3.3. Распределение ресурсов.............................369 6.3.4. Защита............................................ 374 6.4. Способы построения операционных систем...................377 6.4.1. Иерархическая структура............................377 6.4.2. Виртуальные машины................................ 380 6.4.3. Мультипроцессорные системы.........................383 6.5. Примеры реализаций ......................................386 6.5.1. Система UCSD Pascal................................386 6.5.2. UNIX ..............................................388 6.5.3. NOS .............................................. 390 6.5.4. VAX/VMS .......................................... 391 6.5.5. VM/370 ....................•................... . 393 Упражнения....................................................395 Глава 7. Другие компоненты системного программного обеспечения . . 399 7.1. Системы управления базами данных . .....................399 7.1.1. Основные концепции СУБД............................400 7.1.2. Уровни описания данных.............................404 7.1,3. Использование СУБД.................................408 7.2. Текстовые редакторы .....................................411 7.2.1. Описание процесса редактирования...................412 7.2.2. Интерфейс пользователя.............................413 7.2.3. Структура редактора................................417 7.3. Системы интерактивной отладки............................423 7.3.1. Функции и возможности отладчиков...................424 7.3.2. Взаимодействие с другими элементами системы . . . 427 7.3 3. Требования, предъявляемые к интерфейсу пользователя 428 Приложения ................................................. 430 Приложение А. Система команд и способы адресации УУМ/ДС 430 Приложение Б. Таблица символов кода ASCII........434 Прилож ение В. Справочный материал по УУМ/ДС .... 435 Литература ...................................................437 Предметный указатель.....................................440
2 р, 60 «. ISBN 5-03-000011-9 (СССР) ISBN 0-201-10950-6 (США)