/
Author: Фрэнк Т.С.
Tags: программирование микропроцессоры эвм вычислительная техника компьютерные технологии
Year: 1986
Text
ПРЕДИСЛОВИЕ ПЕРЕВОДЧИКА
На примере широко распространенного семейства ЭВМ PDP-11 в книге из-
ложены концепции, лежащие в основе архитектуры большинства современ-
ных ЭВМ. Последовательно и подробно рассматриваются структурные ком-
поненты ЭВМ и их взаимосвязь, программирование на машинном языке и
языке ассемблера. Разбираются вопросы построения простых линейных про-
грамм, организации ветвлений, циклов,обращений к подпрограммам; деталь-
но излагается процесс обработки прерываний. Разъясняется роль стековой
структуры для обращений к подпрограммам и при вложенной обработке пре-
рываний. Рассматривается аппарат макроинструкций и условного ассембли-
рования.
Вместе с читателем автор проходит процесс создания программ, как бы
размышляя вслух, поясняя свои действия, наталкиваясь на препятствия и на-
ходя способы их преодоления. При этом он сознательно ограничивается лишь
той частью всего процесса, которую принято относить к кодированию про-
грамм на машинном языке и языке ассемблера. Поэтому читатель не найдет
здесь сведений о редакторах текста, особенностях конкретных трансляторов,
средствах ведения отладки, командных языках или виртуальной рабочей сре-
де, обеспечиваемой операционной системой. Операционные системы вообще
затрагиваются лишь в той мере, в какой это минимально необходимо для
разработки основного предмета книги.
Изложение хорошо выдержано методически. Большая часть сведений, не-
обходимых для понимания книги, дается в ней самой. Удачно подобранные
упражнения дополняют основной материал книги и позволяют использовать
ее при практическом программировании на конкретной ЭВМ. Искушенному
читателю подача некоторых вопросов может показаться излишне подробной,
но такие места можно пропускать без ущерба для связности изложения.
Книга органично вписывается в ряд книг, изданных в нашей стране по от-
меченным выше вопросам; некоторые из них мы приводим в дополнитель-
ном списке литературы в конце книги. Особенно полезной она будет для тех,
кто работает на вычислительных машинах типа СМ-3,СМ-4,Электроника-60,
Электроника-100-25, MERA-60, MERA-125, ТРА-1140 и др., выпускаемых в
странах СЭВ.
В. М. Северьянов
ПРЕДИСЛОВИЕ
• Эта книга представляет материал для одно семестрового курса по основам
вычислительной техники и программированию на аппаратном уровне и уров-
не языка ассемблера. Добросовестный студент, полностью усвоивший его, бу-
дет хорошо разбираться в устройстве ЭВМ, составе и взаимодействии ее ком-
понентов и различных видах машинных инструкций. Он сможет также до-
вольно умело программировать на языке ассемблера. Самым важным след-
ствием, вероятно, явится то, что успевающий студент окажется прекрасно
подготовленным к освоению целого ряда областей информатики и вычисли-
тельной техники: структур данных, архитектуры ЭВМ, микро-ЭВМ, систем-
ного программирования — короче, всех тех, где требуется определенная глу-
бина понимания работы ЭВМ и машинных языков.
Для освоения материала книги нужны некоторые предварительные, но
умеренные знания. В частности, поскольку аппаратура рассматривается боль-
ше на кон цеп туалъном, нежели на электронном уровне, то никакой предвари-
тельной подготовки по электронике не требуется. Поэтому, когда мы гово-
рим, что контроллер устройства помещает адрес вектора прерываний на ши-
ну данных, электроника, вовлеченная в фиксацию этих данных на шине, не
обсуждается. Аналогично мы не разбираем, как аппаратура справляется с чте-
нием содержимого памяти, приводящим к разрушению информации, хотя
идея и последствия чтения с разрушением информации представляют для нас
интерес. Следовательно, некоторые подробности функционирования аппара-
туры на электронном уровне опущены намеренно, ибо в противном случае
это отвлекло бы от основной цели книги и потребовало бы большей предва-
рительной подготовки, чем мы предполагаем. Когда же студенту известно,
каковы проблемы, как они возникают и почему их необходимо решать, эти
подробности уместнее изучать в курсах по цифровой электронике и архитек-
туре ЭВМ.
Неявно мы полагаем, что студенты, хотя бы мимолетно, имели дело с про-
граммированием, вероятно (или даже предпочтительно), на каком-либо язы-
ке высокого уровня. Общие вопросы программирования — анализ проблем,
построение алгоритмов, разработка программ — обсуждаются лишь вкратце
и предполагаются уже известными. Студенты, не обладающие даже минималь-
ной подготовкой по э-|-им вопросам, окажутся в затруднительном положении,
когда им придется изучать программирование, одновременно овладевая до-
статочно сложными понятиями. Структура программ и стиль программиро-
вания подаются с помощью примеров, но не наставлений. И, наконец, боль-
шинство студентов при выходе на свой первый контакт с ЭВМ испытывают
нетерпение ’’заставить машину что-нибудь сделать”. Это побуждение будет в
6
какой-то мере удовлетворено, так как, изучив эту книгу, студент обретет
способность программировать, когда усвоит необходимый подготовитель-
ный материал.
Самой важной необходимой предпосылкой для каждого, кто изучает эту
книгу, является настойчивость. Программирование на машинном уровне или
уровне языка ассемблера может оказаться делом нелегким даже для опыт-
ного практика, поскольку ошибки в программах здесь встречаются намного
чаще и завуалированы сильнее, чем в языках высокого уровня. Способность
справляться с Ошибками, упорно продолжающими существовать, и извлекать
из этого должные уроки оказывается более полезным качеством студента,
чем любое другое, способное повлиять на освоение излагаемого материала.
Текст книги до некоторой степени машинно-независим. Рассуждения об
оперативной памяти, различных регистрах, внешних устройствах, выполне-
нии инструкций и т. п. применимы к широкому ряду вычислительных машин.
Но для получения опыта по подобным вопросам, позволяющего проиллю-
стрировать и понять аппаратные возможности современной ЭВМ, студент
должен ’’производить вычисления” — писать, отлаживать и выполнять про-
граммы на какой-то конкретной физической машине. В этом отношении ил-
люстративной машиной могла бы быть почти любая ЭВМ — от одной из боль-
ших машин до почти любой микро-ЭВМ. Мы выбрали повсеместно распро-
страненную ЭВМ PDP-11 из-за большого набора ее моделей и потому, что мы
считаем ее архитектуру типичной для современного доступного оборудова-
ния. В большей части текста речь явно идет о структуре этой ЭВМ и наборе
инструкций для нее, но темы рассматриваются с упором на концепции: что
любая машина должна делать для обработки внешнего прерывания, что во-
обще необходимо делать для выполнения правильного возврата из подпро-
граммы и т. п. Следовательно, книга должна познакомить студентов с аппа-
ратурой ЭВМ PDP-11 и ее языком ассемблера таким образом, чтобы знания о
них можно было применить почти на любой другой ЭВМ с минимальной не-
обходимостью переобучения.
Автор сознательно сосредоточился на тех аппаратных возможностях, ко-
торые присуши всем моделям ЭВМ PDP-11. Таким образом, некоторые темы
(например, набор инструкций для операций с плавающей точкой, управление
памятью, кэш-память) упоминаются лишь вскользь (если о них вообще идет
речь). Некоторые особенности машины, не являющиеся универсальными (на-
пример, расширенный набор инструкций), все-таки рассматриваются, но вни-
мание читателя обращается на то, что конкретные особенности или инструк-
ции могут не выполняться на некоторых моделях ЭВМ PDP-11, и зачастую да-
ются предложения по эмуляции таковых на базовых процессорах. Мы также
учитываем то, что некоторые инструкции не на всех моделях выполняются
одинаково. Иногда мы даем замечания об этих различиях, в особенности о
таких, которые относятся к указателю стека (R6) и к схемам адресации, но
те различия, которые не влияют на программирование на данном уровне, во-
обще не обсуждаются.
Хотя полностью независимым от конкретной ЭВМ материал книги не яв-
ляется, он совсем не зависит от операционной системы. При большом числе
выпускаемых производителями операционных систем для PDP-11 вместе с
многочисленными ’’доморощенными” интерактивными системами и система-
7
ми пакетной обработки невозможно полностью охватить эту тему. Ориента-
ция текста на конкретную операционную систему была бы полезной для тех
студентов, на чьей PDP-11 реализовано это конкретное программное обеспе-
чение, но для других, чьи программы должны выполняться на иных системах,
она осталась бы просто балластом. Поэтому полностью опустить эту тему бу-
дет меньшим грехом, чем дать неполное изложение. Тем не менее, некоторые
особенности операционных систем все же рассматриваются. Например, в гл. 9
изучаются вопросы загрузки, перемещения, редактирования объектного фай-
ла, компоновки и выполнения программ, но здесь опять обсуждаются только
концепции, но не то, как конкретная операционная система управляется со
специфическими особенностями этих проблем. Таким образом, студент бу-
дет знать, какая информация должна передаваться от исходной программы
ассемблеру, далее загрузчику и перемещающей программе, затем компонов-
щику и, наконец, процессору для выполнения полученной машинной про-
граммы, но ему ничего не будет сообщаться о том, как конкретная операци-
онная система выполняет эти процессы.
Все это накладывает определенную ответственность на преподавателя, ко-
торый должен дать слушателям соответствующую информацию для конкрет-
ной установки, например форматы команд для ассемблера и компоновщика.
Аналогично могут потребоваться дополнительные материалы и инструкции
по созданию и редактированию текста, а также разъяснения системных сооб-
щений об ошибках. Мы попытались в какой-то мере облегчить эту нагрузку,
включив макроинструкции, позволяющие студентам выполнять операции
ввода и вывода, что является наиболее сложным из того, с чем приходится
иметь дело в курсах, подобных нашему. Эти модули (приведенные в прило-
жении Б) содержат также необходимые подпрограммы для преобразования
форматов данных. Они могут послужить и учебным пособием для студентов,
обладающих определенной подготовкой по программированию на языке ас-
семблера.
Темы по аппаратуре и программированию разрабатываются последователь-
но. За обсуждением оперативной памяти следует рассмотрение содержимого
ячеек памяти, которое, в свою очередь, приводит к идеям представления чи.-
сел, числовых систем ЭВМ и понятию хранимой программы. Аналогично ин-
струкция TRAP естественно приводит к прерываниям и к необходимости ис-
пользования аппаратуры для управления внешними устройствами. Из-за та-
кой структуры текста преподаватель не найдет здесь большого набора тем, из
которых может (или должен) строиться одно семестровый курс. Но для раз-
работки курса существенна большая часть материала. Конечно,можно задер-
жаться на какой-либо конкретной особенности аппаратуры, вероятно на пре-
рываниях, за счет такой темы, как условное ассемблирование. И наоборот,
возможно несколько поверхностное освещение некоторых аппаратных кон-
цепций, чтобы дать время для разработки более углубленных программных
проектов. Но в основном эта книга представляет собой полный односеме-
стровый курс с упором на обеспечение ясности изложения.
Это не просто справочная книга, но инструментальное средство обучения.
В качестве такового она предназначается для осмысленного чтения. Для об-
легчения этого процесса приводятся многочисленные примеры программ и
программных сегментов вместе с их всесторонним обсуждением. Зачастую
8
рассмотрение тем начинается с некоторой легко понимаемой проблемы, по-
сле чего прорабатываются аппаратные особенности и методы программиро-
вания, необходимые для ее решения. Даются все детали, требуемые для
углубленного понимания рассматриваемых концепций, поэтому в некоторых
случаях уровень детализаци может показаться чрезмерным. Однако вины за
это мы не чувствуем. Ведь, например, студент со смутным пониманием того,
в какой точно момент происходит автоматическое увеличение содержимого
регистра во время цикла инструкции, никогда не будет чувствовать себя сво-
бодно с конструкциями наподобие CMP (R2)+, (R2). Для выработки у сту-
дентов опыта и уверенности в себе приводятся многочисленные упражнения.
Они охватывают диапазон от обычных вычислительных проблем и вопросов
(которые можно назвать устными упражнениями) до написания програм-
мных сегментов и полных программ. Большие упражнения, включая задания
на курсовое проектирование, здесь не представлены, хотя они и могут выте-
кать из других упражнений или основного текста. Видимо, каждый отдель-
ный преподаватель, исходя из своих интересов, опыта и квалификации; в
состоянии предложить такие проекты, которые будут лучше согласованы с
возможностями конкретной студенческой аудитории.
Упражнения даются в конце глав, они имеют трехзначную нумерацию, где
первая цифра обозначает номер главы. Вторая — номер раздела, к которому
относится упражнение, а третья задает последовательный номер в пределах
этого раздела.
Автор хотел бы выразить свою признательность редакторам издательства
Prentice-Hall за их помощь и руководящие указания при подготовке этой
книги. Благодарности заслуживает также П. Р. Лаба, бывший директор вы-
числительного центра в Лемойне, за предоставленную возможность использо-
вать оборудование вычислительного центра для подготовки текста, за прочте-
ние и обсуждение нескольких глав и бесчисленные беседы, значительно по-
влиявшие на то, что читатель найдет в этой книге.
Томас С. Фрэнк
ГЛАВА 1. ОСНОВЫ ВЫЧИСЛИТЕЛЬНОЙ ТЕХНИКИ
1.1. ВВЕДЕНИЕ
В этой книге изучаются электронные вычислительные машины, особенно
одна из них ЭВМ PDP-11 фирмы Digital Equipment Corporation. Пояснять,
что означает термин ЭВМ, мы не станем, поскольку удовлетворительного
определения в действительности не существует. Конечно, можно дать аб-
страктное определение конкретной машины в терминах ее архитектуры и
возможностей, но едва ли оно будет иметь отношение к общей концепции
ЭВМ. Количество и разнообразие вычислительный машин в настоящее время
столь велики, что общее, универсально применимое определение представля-
ется недостижимым. В это разнообразие входят специализированные ЭВМ,
наподобие используемых для управления станками и роботами, транспорта-
бельные ЭВМ, применяемые в некоторых автомобилях, все более популяр-
ные и недорогие микро-ЭВМ, средних размеров и стоимости мини-ЭВМ об-
щего назначения, примером которых является PDP-11, и большие и очень
мощные так называемые универсальные ЭВМ. (Надо сказать, что термины
большие и мощные в применении к вычислительным машинам не отличаются
определенностью. ’’Большой, мощный автомобиль” — это хорошо понятно
всем. Но в применении к ЭВМ термин большая обычно означает, что машина
способна хранить данные большого объема, а термин мощная подразумевает
способность обрабатывать большие объемы данных за короткое время) .Да-
же возможности карманных калькуляторов выросли столь сильно, что разли-
чия между этими полезными маленькими устройствами и тем, что мы обыч-
но подразумеваем под ЭВМ, становятся нечеткими. Поэтому наше короткое
обсуждение будет носить характер скорее описания, нежели определения.
ЭВМ — это машина. Вследствие этого она обладает в точности теми харак-
теристиками, которые были заложены при ее создании. Фактически по срав-
нению со многими машинами, с которыми мы имеем дело ежедневно, вычис-
лительная машина весьма проста. Число различных операций, которые она
может выполнять, сравнительно ограничено. ЭВМ кажется сложной потому,
что эти ограниченные действия могут комбинироваться для получения очень
большого разнообразия последовательностей таких действий. В дополнение
к обилию комбинаций простых действий представление о сложности усилива-
ется еще из-за той скорости, с которой эти действия выполняются. Одна мил-
лионная секунды — это подходящая цифра для времени завершения одного
такого действия, хотя ЭВМ, работающие в несколько раз быстрее, не являют-
ся уже чем-то необычным.
Как следует из названия, ЭВМ предназначена, чтобы вычислять, т. е. про-
изводить арифметические операции. Машины, способные выполнять арифме-
10
тические операции, не являются новостью, поскольку механические кальку-
ляторы используются в течение многих десятилетий. Отличительная особен- *
ность ЭВМ заключается в том, что вычисления осуществляется не механи-
чески, а с помощью электроники. Именно это существенно для скорости вы-
числительной машины, поскольку электронные компоненты могут реагиро-
вать на команды намного быстрее механических.
И, наконец, подобно другим типам машин, чтобы быть полезной, ЭВМ дол-
жна быть управляемой. Должна существовать возможность предопределять
те действия или последовательности действий, которые она выполняет. На-
бор инструкций, предназначенных для управления действиями машины, на-
зывается управляющей программой, или просто программой.
1.2. КОНФИГУРАЦИЯ МИНИМАЛЬНОЙ ВЫЧИСЛИТЕЛЬНОЙ СИСТЕМЫ
Хотя удовлетворительного определения ЭВМ нам привести не удалось, мы
можем дать описание того, что означает минимальная вычислительная систе-
ма. Это описание специфично для PDP-11, но оно в равной степени примени-
мо и к широкому ряду других ЭВМ. Для наших целей удобно считать, что вы-
числительная система состоит из следующих компонентов:
центральный процессор, или просто процессор;
оперативная память, или просто память;
внешние устройства;
шины.
В оставшейся части данной главы описываются эти основные компоненты,
но лишь в общих чертах. Большая часть остального текста книги посвящена
изучению того, что собой представляют эти компоненты, каковы их функ-
ции, как они их выполняют, а также как они взаимодействуют друг с другом.
На рис. 1.2.1 показана структурная схема минимальной системы, которая бу-
дет полезной на протяжении всей книги, поскольку наглядно представляет
различные компоненты и отношения между ними.
Шина данных
ЦП
Ьпш:
Адресная шина
Шина управления
Оперативная
память
Внешнее
устройство 1
Внешнее
устройство 2
Внешнее
устройство п
Рис. 1.2.1
11
1.3. ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР
Из всех компонентов, образующих вычислительную систему, централь-
ный процессор (ЦП) является намного более сложным с точки зрения как
физики (электроники), так и его возможностей. Центральный процессор со-
стоит из ряда взаимосвязанных составных элементов, которые мы кратко
здесь опишем.
Арифметико-логическое устройство (АЛУ) . Как следует из названия, оно
предназначено для выполнения арифметических операций — сложения, вычи-
тания и т. п. Кроме того,оно выполняет логические операции, описание кото-
рых дацо в гл. 3.
Устройство управления. Это устройство управляет потоком информации в
процессоре, например передачей данных в АЛУ для арифметической обра-
ботки. В этом же устройстве принимается решение о том, какую инструк-
цию предстоит выполнять процессору, что делается с помощью декодера,
интерпретирующего команды.
Регистры. Это такие электронные устройствам которых может храниться
информация (обычно временно).
В этом месте изложения не стоит ожидать, что читатель четко уловил функ-
циональное назначение процессора, поэтому маловероятно, что приведенные
выше подробности заметно прояснили положение. Достаточно будет сказать,
что активность вычислительной системы концентрируется в основном имен-
но в ЦП, а большая часть текста книги посвящена разъяснению его работы и
возможностей.
1.4. ОПЕРАТИВНАЯ ПАМЯТЬ
В предыдущем разделе упоминалось о том, что в ЦП имеются регистры,
способные хранить информацию. Хотя это верно, при решении типичной за-
дачи, приходится манипулировать тысячами элементов данных, т. е. намно-
го большим их числом, чем всего лишь несколько регистров в процессоре.
Поэтому для ’’удержания” такого значительного объема информации необ-
ходимо какое-то устройство массовой памяти. Подобное устройство мы на-
зываем оперативной памятью. В следующей главе эта концепция рассматри-
вается более подробно.
1.5. ВНЕШНИЕ УСТРОЙСТВА
Вычислительная система с помощью ЦП обрабатывает данные. При этом
оперативная память может использоваться для получения информации и для
хранения про межуточных или окончательных результатов арифметических
операций. Приятно, конечно, построить машину, которая может, например,
складывать два числа, но сс полезность окажется очевидно ограниченной,
если мы не сможем указать, какие числа нужно обрабатывать, и если система
нс сможет сообщить нам результаты затребованной операции. Поэтому для
связи вычислительной системы с внешним миром необходимы какие-то уст-
ройства. Устройства, выполняющие эту задачу, называются коммуникацион-
ными. Они составляют одну категорию внешних устройств ЭВМ.
Внешним устройством является такое, которое подключается непосред-
12
ственно к процессору, но не входит в него как неотъемлемая часть и не сов-
падает с оперативной памятью. Наиболее распространенным внешним устрой-
ством в настоящее время представляется терминал ЭВМ — устройство напо-
добие пишущей машинки, дающее возможность пользователю более-менее
непосредственно взаимодействовать с процессором. Другие внешние устрой-
ства — это АЦПУ, считыватели перфокарт, графопостроители и т. п. Эти уст-
ройства обеспечивают взаимодействие между процессором (и оперативной
памятью) и внешним миром. Другую категорию внешних устройств состав-
ляют запоминающие устройства большого объема. Они обеспечивают расши-
рение оперативной памяти, где хранится информация, которую обычно не-
возможно использовать непосредственно из внешнего мира. Примерами мо-
гут служить магнитные диски и барабаны, а также носители информации на
магнитной ленте.
1.6. шины
Неявно в наших рассуждениях присутствует понятие транспортировки
информации внутри вычислительной системы — данные пересылаются в опе-
ративную память и из нее, а также между ЦП и различными внешними устрой-
ствами. Информация передается с помощью электрического тока, проходя-
щего по совокупностям проводов, называемым шинами. На рис. 1.2.1 пока-
заны три такие шины (три набора проводов), обозначенные как адресная
шина, шина данных и шина управления. Несмотря на то, что мы далеко не
сразу познакомимся со способами использования этих шин, имеет смысл
предостеречь читателей от придания им какого-либо более глубокого смыс-
ла, чем описанный, — это наборы проводов, по которым может передаваться
информация между компонентами внутри вычислительной системы.
ГЛАВА 2. ОПЕРАТИВНАЯ ПАМЯТЬ И АРХИТЕКТУРА ЭВМ
2.1. ИНФОРМАЦИЯ
Формальное понятие информации (что она собой представляет, как пере-
дается, как на нее влияет ’’загрязнение шумом” и т. п.) является настолько
сложным, что ему посвящены математическая, физическая и инженерная
дисциплины. И хотя при изучении вычислительных машин понятие информа-
ции оказывается центральным (ЭВМ, в конце концов, перерабатывает инфор-
мацию), тип информации, с которым нам придется иметь дело, настолько
прост, что с помощью очень несложных методов можно получить вполне
удовлетворительные результаты.
На нас постоянно обрушиваются огромные потоки информации, в основном визуаль-
ной и звуковой, и вся эта информация так или иначе обрабатывается. Большая ее часть
просто игнорируется (обычно на уровне подсознания). Например, когда мы смотрим
на некоторый объект на другом конце комнаты, то получаем намного больше информа-
ции, чем поступает от интересующего нас объекта, но эта посторонняя информация рас-
сматривается как ’’шум” (нежелательная информация), и нам удается достаточно эф-
фективно ее подавлять. Помимо большого объема получаемой информации велика еще
и ее сложность. Например, в Третьей симфонии Бетховена или в получасовой телевизи-
онной программе записано огромное количество информации. Другой противоположно-
13
стью может служить звук автомобильного сигнала. Если игнорировать такие качества,
как высота (частота) и продолжительность звука, то обнаруживается, что переданная
информация весьма проста. Фактически доступной стала только одна порция информа-
ции — присутствие звука как противоположность его отсутствию. Способ обработки
этой конкретной порции информации может быть довольно сложным и зависит от об-
стоятельств (наща реакция на эту простую информацию будет, вероятно, разной, в зави-
симости от того, стоим ли мы на тротуаре или на проезжей части) .
Другой понятный пример этого типа информации: нужно пойти в комнату, чтобы по-
смотреть, горит ли там свет или нет. Передаваемая здесь информация опять будет того
же простого типа - наличие или отсутствие чего-либо. Некоторая часть ежедневно обра-
батываемой нами информации имеет эту простую природу ”да — нет” или ’’включено —
выключено”, хотя подавляющая часть информации намного сложнее. Одной из особен-
ностей, делающих ЭВМ столь концептуально простой, является тот факт, что она имеет
дело с информацией типа ”да — нет”. \
2.2. ХРАНЕНИЕ ИНФОРМАЦИИ
Понятие хранения информации всем понятно. Книги, кинопленки, магнит-
ные ленты хранят записанную информацию. Все это - запоминающие устрой-
ства. Познакомимся вкратце с человеческим мозгом как с запоминающим
устройством, в частности с той частью мозга, которая имеет дело с памятью.
Как мозг запоминает данные, известно слабо, но мы ограничимся лишь общи-
ми наблюдениями по поводу этого процесса.
Человеческая память обладает тем свойством, что информация может запоминаться
в ней для последующего извлечения, причем процесс сохранения представляется нам
распадающимся на три больших части. Информация, воспринимающаяся как малозначи-
мая, записывается в кратковременную память. Эта информация доступна в течение ко-
роткого периода времени, после чего исчезает. Например, мы можем, вероятно, запом-
нить, что ели на завтрак сегодня или даже вчера утром, но вряд ли в состоянии вспом-
нить, что было на завтрак семь лет назад. Другая информация помещается в долговре-
менную память без каких-либо наших сознательных усилий. Мы легко можем вспом-
нить по желанию какие-то особенно приятные события. К несчастью, мы легко вспоми-
наем и очень неприятные события, но, в противополояшость магнитофону, мы не имеем
средств для стирания этой информации. И, наконец, некоторая информация сохраняется
в долговременной памяти с помощью сознательных усилий для ее запоминания. Чита-
тель не вспомнит те муки, которые он прошел, заучивая таблицу умножения, но он, ко-
нечно же, помнит саму таблицу. Именно этот последний тип хранения информации пред-
ставляет для нас особый интерес.
Запоминание информации полезно по трем принципиальным причинам.
Во-первых, такая информация является определенной, а не случайной, как
это зачастую бывает в кратковременной памяти, поскольку мы сознательно
выбираем в точности ту информацию, которую хотим запомнить. Во-вторых,
с помощью некоторого сознательного усилия эта информация может быть в
памяти сохранена. И, наконец, эта информация может быть извлечена всякий
раз, когда она потребуется. (Например, плотник может сознательно запом-
нить, что фактические размеры доски 2x4 равны 1,5 на 3,5 дюйма; эта ин-
формация будет доступной всегда, когда нужно.) От памяти ЭВМ мы требу-
ем именно этих трех характеристик: способности выбирать, сохранять и из-
влекать информацию.
Мы кратко рассмотрели человеческую память, чтобы проиллюстрировать
некоторые полезные аналогии с устройствами памяти ЭВМ. У нас нет намере-
14
ния наделять вычислительные машины и их устройства памяти человечески-
ми чертами — концепция ’’гигантского моэга”, так часто ассоциировавшаяся
с первыми ЭВМ, почти совсем сошла со сцены. В то же время вычислитель-
ные машины разрабатываются людьми с целью имитации некоторых функ-
ций человеческого мозга, поэтому вполне естественно, что существуют
аналогии, а терминологии присущ определенный анатомический характер.
2.3. ЗАПОМИНАЮЩИЕ УСТРОЙСТВА ЭВМ
В предыдущем разделе говорилось о том, что для эффективного использования па-
мяти ЭВМ мы должны обладать возможностью хранить некоторую выбранную информа-
цию так, чтобы позднее ее можно было извлекать. В гл. 1 говорилось о том, что вычис-
лительные машины - это электронные устройства. Следовательно, они управляются с
помощью электрического тока. Поэтому в поиске устройств, способных работать как
запоминающие для ЭВМ, надо обратить внимание на такие компоненты, которые пред-
сказуемым образом реагируют на наличие или отсутствие электрического тока.
В качестве первого шага в этом направлении рассмотрим конденсатор, обладающий
свойством заряжаться, когда к нему прикладывается напряжение, т. е. потенциал одной
обкладки конденсатора становится равным, например 1 В, а потенциал другой — равен
О В (см. рис. 23.1, где подполагается, что источник имеет напряжение 1 В). После заряд-
ки конденсатор сохраняет разность потенциалов на своих обкладках даже тогда, когда
источник напряжения удаляют. Поэтому о конденсаторах часто говорят, что они ’’сохра-
няют” электричество.
Конденсатор может находиться в Заряженном состоянии, описанном выше, или в про-
тивоположном, т. е. разряженном состоянии. Состояние конденсатора в любое время
легко можно определить. Для этого к обкладкам конденсатора необходимо подключить
измерительный прибор, как это показано на рис. 2.3.2. Если конденсатор заряжен, то
через измерительный прибор потечет ток, который вызовет отклонение индикаторной
стрелки. Когда конденсатор разряжен, такого отклонения ие происходит.
Конденсатор представляется таким устройством, которое мы разыскиваем для запо-
минания информации, поскольку очевидно, что он может пребывать в одном из двух
различных состояний - заряженном или разряженном. Устройства, которые могут нахо-
диться в одном из двух определенных и различимых состояний, называются бистабиль-
ными. Более того, состояние конденсатора может быть установлено сознательным дей-
ствием — его зарядкой или разрядкой (разрядка конденсатора достигается просто пу-
тем соединения двух его обкладок проводником, т. е. закорачиванием его, так что на
обкладках устанавливается одинаковый уровень напряжения). И, наконец, состояние
конденсатора может быть определено, например, с помощью измерительного прибора.
Таким образом, конденсатор может использоваться для сохранения извлекаемой инфор-
мации. Однако обратите внимание, насколько груба эта информация - может сохранять-
ся только одна из двух возможных ее частей. Информация в конденсаторе относится к
типу ”да - нет” или ’’включено - выключено”, что дает не больше, чем наблюдение то-
го, горит или не горит свет в комнате.
Конденсатор
Источник напряжения
Рис. 2.3.1
Конденсатор
Измерительный прибор
Рис. 2.3.2
15
Как запоминающее устройство конденсатор обладает некоторыми неприятными свой-
ствами. На концептуальном уровне они для нас интереса не представляют, поскольку с
ними можно справиться соответствующими инженерными методами, но во всяком слу-
чае упомянуть о них стоит. Читателю может показаться, что, когда конденсатор заряжен,
он остается в заряженном состоянии неопределенно долго. Фактически же с течением
времени заряд конденсатора теряется, или ’’стекает”. Поэтому может потребоваться вре-
мя от времени ’’обновлять” состояние конденсатора для гарантии того, что оно не будет
ошибочно интерпретировано как разряженное, тогда как оно должно быть заряженным.
Вторая проблема несколько серьезнее, но с ней тоже можно справиться. Когда мы хо-
тим определить состояние данного конденсатора, то подключаем к нему измерительный
прибор, позволяющий обнаружить прохождение тока. К несчастью, это действие, если
конденсатор был заряжен, приводит к его разрядке, переводя тем самым в противопо-
ложное состояние. Конечно, если мы обнаруживаем, что конденсатор был заряжен, то
можем перезарядить его до первоначального состояния, так как мы уверены, что разря-
дили его при определении состояния.
Когда конденсатор установлен в одно из своих состояний, мы говорим, что в него
записана информация. При определении состояния конденсатора мы говорим, что ин-
формация считывается. Если информация из конденсатора считывается так, что она раз-
рушается и, следовательно, требуется ее восстановление, то такая операция называется
чтением с разрушением информации.
Хотя мы располагаем теперь устройством, способным сохранять информацию, сле-
дует заметить, что из-за ограниченности сохраняемой информации требуется дальнейшая
проработка с целью получения схемы, пригодной для практики. Это будет сделано в
следующем разделе и в остальной части главы, но перед тем, как развивать эти идеи,
познакомимся вкратце еще с одним бистабильным устройством, широко используемым
в качестве запоминающего устройства ЭВМ. Это магнитный сердечник.
Магнитный сердечник представляет собой ’’бублик” из материала, способного намаг-
ничиваться. Если через отверстие в ’’бублике” проходит провод, по которому в опреде-
ленном направлении пропускается электрический ток, то сердечник намагничивается в
одном из возможных направлений — по часовой стрелке или против - в зависимости от
направления тока. На рис. 2.3.3 результатом прохождения тока через проводнике ука-
занном направлении будет намагничивание сердечника по часовой стрелке, если смот-
реть сверху. Если ток направить в противоположном направлении, то сердечник намаг-
нитится в другом направлении. Таким образом, информация (направление намагничива-
ния) может быть записана в сердечник с помощью пропускания тока по проводу записи
в одном или в другом направлении. После того как сердечник намагничен в каком-либо
направлении, необходимость в сохранении тока в проводе записи отпадает, поскольку и
при отсутствии тока сердечник останется намагниченным в данном направлении неопре-
деленно долго. Именно эта особенность — отсутствие необходимости в обновлении со-
стояния — делает магнитные сердечники столь привлекательными для создания запоми-
нающих устройств ЭВМ.
Состояние сердечника (направление намагничивания) определяется относительно
легко. Через отверстие в сердечнике продевается другой провод, называемый прово-
дом чтения, или проводом считывания. Предположим, что мы теперь пропускаем ток по
проводу записи в фиксированном направлении, как показано на рис. 2.3.4. Произойдет
одно из двух. Если сердечник намагничен по часовой стрелке (если смотреть сверху),
то ничего не случится, поскольку ток в проводе записи стремится намагнитить сердеч-
ник в его теперешнем направлении. Но если сердечник намагничен против часовой стрел-
ки, ток вызовет намагничивание его в противоположном направлении (почасовойстрел-
ке) . При изменении направления намагничивания на противоположное меняется магнит-
ное поле внутри сердечника. Это изменение поля обнаруживается потоку, возникающе-
му в проводе чтения. Таким образом, чтобы ’’прочитать” состояние сердечника, нужно
пропустить ток по проводу записи в известном направлении, посмотреть, наводится ли
16
Рис. 2.3.3
ток в проводе чтения. Однако обратите внимание на то, что здесь опять происходит чте-
ние с разрушением информации, поэтому может потребоваться ее перезапись, чтобы со-
хранить состояние сердечника после его считывания.
Несмотря на положительные свойства магнитных сердечников (не нуждаются в об-
новлении и даже не требуют питания для поддержания состояния) они относительно до-
роги и медленно реагируют на изменение тока. По этим причинам запоминающие устрой-
ства на магнитных сердечниках, хотя и сыграли очень большую роль в истории развития
вычислительной техники, теперь повсеместно заменяются более быстрыми и менее доро-
гими транзисторными устройствами.
2.4. ЭЛЕМЕНТЫ ПАМЯТИ
Конденсаторы и сердечники — это два примера бистабильных устройств,
способных находиться в одном из двух состояний, которые можно устано-
вить (записать) с помощью электрического тока и можно позднее опреде-
лить (прочитать) с помощью каких-то электрических средств. Эти свойства
существенны для запоминающих устройств. Информация, которая может по-
мещаться в такие устройства, фундаментальна, насколько это возможно: она
имеет природу ”да — нет”. Действительно, меньшая информация вообще уже
не информация. В остальной части текста нас не будет интересовать, какое
устройство используется для хранения информации, для нас достаточно знать,
что с этой целью применяется какое-то бистабильное устройство. Само это
устройство мы будем называть ячейкой памяти, а его состояния обозначим
просто через а и Ь, не делая никакого различия между заряжено или разряже-
но, по часовой стрелке или против и т. п.
Количество информации, которое может удерживаться в одной ячейке па-
мяти, настолько мало, что оно почти бесполезно, исключая лишь очень не-
обычные ситуации. Однако мы рассмотрим элемент памяти, составленный из
двух таких ячеек, состояние каждой из которых может быть записано и про-
читано независимо от другой. ’’Значения”, которые может иметь такой эле-
мент, показаны в табл. 2.4.1, где Ci иС2 — две индивидуальные ячейки, со-
ставляющие элемент.
Такой элемент может сохранять четыре различные порции информации,
что, конечно, является продвижением вперед. Обратите, однако, внимание,
что индивидуальные ячейки и С2 должны быть упорядочены, чтобы в
них можно бьвю хранить столько информации. Одна из ячеек, которую мы
здесь обозначили Ci, должна быть первой или левой, так как иначе нельзя
будет различать комбинации ab и Ъа.
17
Таблица 2.4.1
Таблица 2.4.2
U2 ~ С, С2 »3~C1 С2 С3
а а а а а
a b а а b
b a aba
b b abb
------- b а а
b а b
b b а
b b b
Сделаем еще один шаг в этом направлении, скомбинировав на этот раз в
элементе памяти три ячейки. Как видно из табл. 2.4.2, теперь есть восемь раз*
личимых наборов состояний, в которых могут находиться эти три ячейки.
Мы опять предполагаем, что ячейки независимы: установка одной ячейки ни
в коей мере не влияет на установку любой другой ячейки в элементе.
Нетрудно проверить, что при независимом объединении в элемент памяти
п таких бистабильных ячеек элемент памяти Un может находиться в любом
из 2" состояний. Каждое такое состояние представляется строкой букв а и Ь,
например abbbaabaaabb.
Таким образом, в запоминающем устройстве ЭВМ можно сохранять столь-
ко информации, сколько потребуется; для этого надо просто использовать
элемент памяти с достаточным числом ячеек. Но какого типа информацию
мы сохраняем? То есть, как строки из букв а и b будут интерпретироваться?
К этому вопросу мы вернемся позднее, после краткого отступления, необ-
ходимого, чтобы познакомиться с основами числовых систем.
2.5. ПРЕДСТАВЛЕНИЕ ЧИСЕЛ
Когда мы говорим ’’число 142”, нас можно обвинить в неточности или, по
крайней мере, в небрежности. Этой фразой мы хотели сказать ’’число, пред-
ставление которого есть 142”. Эквивалентно было бы утверждение ’’число,
представление которого есть CXL П”. Если мы хотим быть точными, то долж-
ны признать, что есть различие между числом и одним из его многочисленных
представлений, и это различие будет иметь для нас некоторые последствия в
этом и последующих разделах.
Число — это абстрактное, к тому же довольно трудное понятие. Когда.мы
говорим ”на флаге тринадцать полос”, то приписываем этому набору полос
абстрактное качество ’’являться тринадцатью”. То же можно сказать и о че-
тырех состояниях, в которых могут находиться две независимые ячейки па-
мяти: эти квартеты обладают абстрактным качеством ’’являться четверка-
ми”. 13 во фразе ”13 полос” — это символическое представление абстракции,
т. е. числа по имени тринадцать. Именно это символическое представление
мы и хотим вкратце рассмотреть.
Если 142 — это символическое представление абстрактного числа, которое
мы называем ’’сто сорок два”, то какую роль играют отдельные символы 1,
18
4 и 2 и почему используются именно эти символы? Представление, состав-
ленное из символов 1,4 и 2, исходит из следующего:
142 = 1 • 100 + 4 • 10+2 = 1 • 102 + 4 • 101 + 2 • 10°.
Это выражение можно также переписать как
142 = 1 • х2 +4 • х1 + 2 -х°
при условии, что х - 10. То есть, символическое представление числа есть по-
лином от х, где х = 10. По соглашению мы не пишем в качестве представле-
ния числа сам полином, а записываем просто его коэффициенты (в данном
случае 1,4 и 2). Как известно читателю, для обращения с этими коэффициен-
тами требуется определенное внимание. Коэффициенты записываются слева
направо, причем первой идет самая большая степень 10, а коэффициенты с
нулевым значением тоже должны учитываться, поскольку было бы неверно
представить 1 • 104 + 4 • 102 + 2 • 101 как 142.
К символам, используемым для представления (а именно, 0,1,... , 9) не
предъявляется каких-то специальных требований за исключением того, что
они должны быть различимы и нам должно быть известно значение каждого
из них. Теоретически подходящими могут оказаться и такие символы, как
хотя обычные цифры настолько универсальны, что замена сим-
волов была бы лишенной смысла, выбор х равным 10 без сомнения обус-
ловлен фактом наличия у человека десяти пальцев, что и привело нас к опре-
деленной системе счета. Мы считаем следующим образом (начало счета с ну-
ля, вероятно, выглядит несколько необычно):
отсутствие-пальцев, один-палец, два-пальца,..., девять-пальцев
одинчюлный-набор-пальцев-плюс-отсутствие-пальцев, о дин-пол ный-набор-
папьцев-плюс-один-палец,...
что соответствует такому записанному представлению:
0,1,2,..., 10,11,...
(Устное произнесение становится несколько неудобным для представлений,
превосходящих 99 — ”девять-полных-наборов-пальцев-плюс-девять-паль-
цев”.) Если бы у нас было на каждой руке по три пальца, то без сомнения
счет происходил бы так: отсутствие-пальцев, один-палец,. . . , пять-пальцев,
один-полный-набор-пальцев-плюс-отсутствие-пальцев, . . . или символи-
чески — 0, 1,. .. , 5, 10, 11 и т. д. Величина х (’’число пальцев”) в представле-
нии числа называется основанием системы представления чисел (системы
счисления). Таким образом, в первом примере основание было равно 10, а
во втором - 6.
Как в свете этого мы должны интерпретировать полином
142 = 1 • х2 + 4 • х1 + 2 • х °?
Ответ зависит от того, какое значение используется для х. Если х равно 10,
то 142 означает 1 • 100 + 4 • 10 + 2. Но если х равно 6, то 142означает 1 • 62 +
+ 4 • 61 + 2 • 6 0 = 1 • 36 + 4 • 6 + 2, что в десятичной системе счисления бы-
ло бы представлено символом 62. Уравнение 142 = 62, конечно, бессмысли-
ца, но читатель должен увидеть проблему — обе части этого уравнения отсы-
лают к разным основаниям представления чисел. Недоразумение можно лик-
видировать, записав, например, 142 (при х = 6) =62 (прих = 10) или, более
19
компактно, 1426 = 6210. Читателю рекомендуется проверить, что 142ю =
= 3546.
Чтобы представлять числа по основанию 6, надо придумать шесть разли-
чимых символов для использования в представлении в качестве цифр. Мы ис-
пользовали 1, 2, 3, 4 и 5, поскольку это уже готовые символы с хорошо из-
вестным значением. Предположим, однако, что мы решили представлять
числа по основанию 16. Тогда нам необходимо 16 различимых символов, но
среди обычных цифр есть только десять. Поэтому приходится изобрести не-
сколько новых символов, и мы выбираем А, В, С, D, Е и F, где А обознача-
ет то, что по основанию 10 обозначает десять, В заменяет одиннадцать, . . .
. . . ,F заменяет пятнадцать. Таким образом, схема счета по основанию шест-
надцать (называемая шестнадцатеричной) выглядит так:
0,1,2,.. ., 8,9,А, В, С, D, Е, F, 10, 11,.. . ,
19, L4,1В, 1С, ID, 1£, 1F, 20,. . . , 98,99,9А,.. . ,
9F,Л0,Л1,.. .
(Читателю рекомендуется проверить, что 142ю =8£’16.)
В начальной школе мы постигаем десятичную арифметику, включая алго-
ритмы (или процедуры) сложения, вычитания и т. п. и сопутствующую об-
работку ’’переносов” и ’’заемов”. Легко можно установить, что эти алгорит-
мы, хотя и предназначенные специально для десятичной арифметики, в дей-
ствительности не зависят от основания — так же хорошо они работают и при
основнии 16 или 6. Таким образом, для выполнения арифметических вычис-
лений по основаниям, отличным от 10,не требуется изучать чего-либо нового, а
нужно всего лишь преодолеть отсутствие привычки к этим основаниям. Для
иллюстрации этого факта ниже даются несколько примеров таких процедур.
Читателю рекомендуется проверить правильность каждой из них, вероятно,
преобразуя числа к более привычному основанию 10 и выполняя вычисления.
Будет также полезно попрактиковаться с помощью упражнений, приведен-
ных в конце главы.
12738 £>А516 1426
+56468 -2F9i6 х 546
71418 ААС16 10526
12346
134326
Совершенно очевидно, что есть бесконечно много различных оснований,
которые можно использовать для представления чисел. Хотя основания,
много большие 16 или 20, представляются совершенно неразумными из-за
большого количества требуемых цифр и необходимости помнить, что каждая
из них обозначает, теоретически верхнего предела для основания системы
счисления нет. Но есть нижний предел, а именно 2. При таком основании по-
лучается система двоичного представления чисел. В пользу этой системы го-
ворит то, что при основании 2 для представления любого числа требуются
только две цифры — 0 и 1. Поэтому например,
14210 = 100011102 =
= 1 • 27 + 0 • 26 + 0 • 25 + 0 • 24 + 1 • 23 + 1 • 22 +
+ 1 • 21 + 0 • 2 °.
20
Очевидный недостаток представления по основанию 2 состоит в том, что да-
же для чисел средней величины требуется много двоичных цифр.
Итак, мы видим, что число (как абстрактная концепция) может быть пред-
ставлено целым рядом способов, в зависимости от выбранного основания си-
стемы счисления. Арифметика над числами по любому основанию не более
сложна, хотя, вероятно, менее цривычна, чем по основанию 10. Большие ос-
нования для представления чисел приводят к компактным выражениям, со-
стоящим из небольшого числа цифр, для маленьких оснований верным будет
обратное утверждение. Опыт говорит, что основание 10 для нас наиболее
удобно, тогда как основание 2 кажется наименее желательным, если исхо-
дить из компактности представления. Но, несмотря на эту неэффективность
представления, именно двоичная система будет иметь для нас особое значение.
2.6. ИЗМЕНЕНИЕ НОТАЦИИ
В разд. 2.3 мы познакомились с устройствами, способными находиться в
одном из двух состояний, а и Ь. Затем мы скомбинировали такие устройства
в группы (или элементы памяти) длиной п, причем каждая группа может
принимать ’’значение” таких состояний длиной л, например abbbaabaaabb. Ка-
кую информацию переносит подобная строка — можно только гадать, однако
почти тривиальное изменение нотации станет более красноречивым.
Предположим, что вместо нотации а и Ъ подставляются соответственно
символы 0 и 1. Тогда значение упомянутого элемента памяти, а именно
abbbaabaaabb, будет равно 011100100011. И если интерпретировать эту стро-
ку нулей и единиц как двоичное (по основанию 2) представление числа (ко-
торое равно 1827ю), то элемент памяти будет содержать весьма значимую
информацию. Возникает искушение сказать, что этот элемент памяти’’содер-
жит число 011100100011 = 1827ю’, но это было бы явной бессмыслицей. Эле-
мент памяти состоит из отдельных ячеек памяти, каждая из которых может
находиться в одном из двух состояний. К неточности нас приводят нотация
(0 и 1) и наша интерпретация. Если элементы памяти что-либо и содержат,
так это последовательности состояний ячеек. Тем не менее числовая интер-
претация столь привлекательна, что мы не в силах сопротивляться искуше-
нию говорить, например, что элемент памяти ’’содержит двоичное число
110101 или, что эквивалентно, десятичное число 53”. Поэтому здесь и в ос-
тальном тексте книги мы будем говорить, что элемент памяти содержит чис-
ло, или что число находится в элементе памяти. Восприятие содержимого
элементов памяти как чисел — это наиболее естественный взгляд на подоб-
ные устройства, и, по крайней мере в данном случае, такая неточность нам не
повредит, ведь упорное следование описанному выше формализму только за-
труднило бы изложение.
Теперь мы можем записывать конкретные числа в их двоичном формате в
элементы памяти так, что позднее они могут быть прочитаны. Однако пред-
полагается, что ЭВМ обрабатывает данные, поэтому, если мы не предложим
каких-то методов воздействия на эти сохраняемые в памяти числа, от такой
схемы будет мало проку.
21
2.7. МАШИННЫЕ СЛОВА И СИСТЕМЫ СЧИСЛЕНИЯ
Обсуждаемые так долго элементы памяти состоят из отдельных ячеек па-
мяти, способных удерживать двоичные представления чисел. Размер чисел,
которые могут быть представлены, зависит от числа ячеек памяти, образу-
ющих элемент. Конструируя оперативную память ЭВМ из таких состоящих
из ячеек памяти элементов, мы должны помнить о том, что речь идет не об
одном элементе, — устройства оперативной памяти вычислительных машин
состоят о бычно из тысяч, сотен тысяч или даже из миллионов таких элементов.
Решение о числе ячеек, образующих элемент памяти,обусловливается тем, что
с инженерной точки зрения каждому элементу полагается (а практически он
должен) состоять из одного и того же числа ячеек (существует несколько
ЭВМ, выходящих за это ограничение, но в семействе PDP-11 таких нет) . По-
этому принимаемое на данном этапе решение будет иметь далеко идущие по-
следствия. Если мы решим, что каждый элемент будет состоять из трех яче-
ек, то возможными станут двоичные представления чисел от 0 до 7 (в двоич-
ном виде от ООО до 111). Если в элемент группируются 15 ячеек, то возмо-
жен намного более широкий диапазон чисел — от 0 до 32767. Намереваясь
строить элементы памяти из многих ячеек, чтобы они были способны хра-
нить очень большие числа, мы должны помнить об определенных экономи-
ческих ограничениях. Экономически просто невозможно изготовить много
устройств оперативной памяти, состоящих из произвольно большого числа
элементов памяти, каждый из которых состоит из произвольно большого
числа отдельных ячеек. Поэтому принимаемое решение должно заключать в
себе компромисс между стоимостью памяти и числом элементов памяти и
ячеек, составляющих каждый элемент. Конечно, независимо от того, какое
решение принимается, существует верхний предел числа, которое может быть
представлено в любом элементе, поскольку любой элемент составляется из
конечного числа ячеек.
Настала пора несколько изменить нашу терминологию, чтобы она соответ-
ствовала общепринятой в настоящее время. Элементы памяти, образующие
оперативную память ЭВМ, называются словами, и каждое слово состоит из
фиксированного числа ячеек памяти, называемых битами (бит — это сокра-
щение от бинарная, т. е. двоичная, цифра1). Поэтому, когда мы говорим, на-
пример, об ЭВМ с 24-битовым словом, то имеем в виду такую ЭВМ, опера-
тивная память которой организована из слов (элементов), каждое из кото-
рых состоит из 24 бит (ячеек).
. Как уже отмечалось, размер слова (в битах)в выбранный для оперативной
памяти ЭВМ, предопределяет наибольшее число, которое может удерживать-
ся в любом элементе памяти. Некоторые популярные сейчас размеры — это
8 бит на слово (в большинстве своем используются в устройствах памяти ми-
кро-ЭВМ) , 12 и 16 бит на слово (чаще всего применяются в мини-ЭВМ) и 24,
32, 36 и 48 бит на слово. Сейчас мы изучим поведение ЭВМ с четырехбито-
вым словом. Очевидно, что такой размер слова, обеспечивающий хранение
только чисел (в десятичном виде) от 0 до 15, слишком мал, чтобы представ-
лять какое-либо практическое значение. Тем не менее концепции, которые
здесь обнаруживаются, без изменения могут быть перенесены на машины с
1 По-английски - binary digit. - Прим, перев.
22
О Битовая позиция
Рис. 2.7.1
любым размером слова, однако с четырехбитовым словом легче управлять-
ся, чем с длинными словами.
Каждое четырехбитовое слово мы представляем себе состоящим из четы-
рех ячеек, поименованных бит 3, бит 2, бит 1 и бит 0, как показано на рис.
2.7.1. Каждый бит может иметь значение О или 1. Обратите внимание, что наз-
вание битовой позиции соответствует степени 2, представленной этим битом.
Таким образом, например, бит 2 соответствует 2* = 4. Бит 3 называется са-
мым старшим битом слова, а бит 0 самым младшим битом (хотя такая нуме-
рация битов вполне естественна, в определенной степени она произвольна; в
некоторых вычислительных системах используется противоположная терми-
нология — самый старший бит называется битом 0, а самый младший — би-
том 3).
Теперь нам необходимо сделать некоторые предположения о возможно-
стях ЦП. В частности, мы полагаем, что ЦП способен увеличивать (прибав-
лять 1) четырехбитовые слова, так что, например, слово 1001 может быть
увеличено до значения 1010. Кроме того, мы полагаем, что ЦП может допол-
нять четырехбитовые слова, т. е. в нем имеетсд соответствующая схема для
реверсирования состояния каждого бита в четырехбитовом слове, так что
слово 1001 будет дополнено до ОНО. (В следующей главе мы увидим, что
разработка схем для выполнения таких арифметических задач не представля-
ет большого труда.) Имея в виду эти предположения, рассмотрим такое че-
тырехбитовое слово, все биты которого равны 0 и представляют, таким обра-
зом, число 0000 = 0. Если это слово увеличить, его содержимым станет 0001 =
= 1. Продолжая увеличивать это слово, мы получим результаты, показанные
в табл. 2.7.2. Если мы попытаемся теперь увеличить слово, содержимое кото-
рого стало равным 1111, то столкнемся с явлением, которое будет иметь
большое значение:
1111
+ 1
10000
Для представления результата сложения, равного в десятйчном виде 16,
нужно пять битов, тогда как наше слово содержит только четыре бита. Еди-
ница, получаемая в результате переноса из бита 3 в несуществующий бит 4,
теряется, и, таким образом, показанная выше арифметическая операция дол-
жна выглядеть так:
1111
+ 1
0000
или 1111 + 0001 = 0000 (в десятичном виде: 15+1 =0) ,чп\очевидно, невер-
но. Давайте посмотрим, сможем ли мы справиться с этой проблемой.
Во-первых, можно утверждать, что неприятность не исходит из схемы уве-
личения, имеющейся в процессоре, поскольку увеличение выполняется в точ-
Таблица 2.7.2
Содержимое слова
Двоичное Десятичный эквивалент
0000 0
0001 1
0010 2
ООП 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 10
1011 11
1100 12
1101 13
1110 14
1111 15
ности так, как было заложено при разработке.
В самом деле, любой другой четырехбитовый
результат мог бы рассматриваться как ’’непра-
вильный”. Трудность заключена в том, что сло-
во содержит только четыре бита и, таким обра-
зом, не может удерживать правильный пятиби-
товый результат. Но если бы мы выбрали боль-
ший размер слова, то просто отсрочили бы воз-
никновение проблемы; ведь независимо от раз-
мера слова в конце концов мы столкнемся с пе-
реносом из самого старшего бита. Частично не-
приятность обусловлена нашей интерпретацией
этих четырех битов (строчек нулей и единиц)
как представления двоичного числа. Такая ин-
терпретация хорошо нам служила до этого мо-
мента, но она терпит неудачу, когда слово содер-
жит конкретную битовую конфигурацию 1111.
Таким образом, нам, вероятно, даже не следует
говорить, что результат неправильный, — то, что
случилось, было полностью предсказуемым.
Это явление оказывается результатом нашей интерпретации содержимого
слова памяти как представления двоичного числа вместе с тем фактом, что
любое такое слово может удерживать только конечное число битов. Таким
образом, числовая система ЭВМ конечна и, если рассматривать ее с точки зре-
ния последовательных увеличений слова, циклично, т. е. она замыкается сама
на себя (рис. 2.7.3).
Вернемся к ’’уравнению”, которое побудило это пос/еднее исследование:
1111 + 0001 = 0000. Хотя этот результат неправилен, т .ы определили, что он
является следствием конечности числовой системы и нашей интерпретации
содержимого слова памяти. Но предположим, что мы заново интерпретируем
представление числа 1111 как отрицательную единицу, а не как положитель-
ное пятнадцать. Уравнение становит-
ся правильным: —1 + 1 =0. Но что тог-
да должно означать 1110? Посколь-
ку 1110 на единицу меньше, чем 1111,
1110 должно интерпретироваться как
—2. Если мы продолжим в таком ду-
хе, то должны в конце концов заклю-
чить, что 0001 = —15, а на следующем
шаге 0001 — 1 =0, что ведет к утвер-
ждению —15 — 1 =0, являющемуся
не более удовлетворительным, чем
15 + 1 - 0. В действительности здесь
нет ничего нового. Наша новая ин-
терпретация просто накладывает на
эти четырехбитовые слова другую
числовую систему и с точки зрения
конечности системы, ее цикличности
Увеличение
Рис. 2.7.3
24
и ”неправильности”некоторых ариф- таблица 2.7.4 метических операций она не более
удовлетворительна, чем предыду- щая схема с положительными чис- лами. Тем не менее будет полезным расширить табл. 2.7.2 И ВКЛЮЧИТЬ В Двоичное Число, интерпретируемое как
Положительное Отрицательное
нее эту вторую интерпретацию (таол. 2.7.4). оооо Теперь мы располагаем двумя 0001 различными интепретациями наших 0010 четырехбитовых слов в качестве 0100 представления чисел и намереваем- 0101 ся ввести еще одну. Практически в 0110 этих словах нам бы хотелось хра- ош нить как положительные, так и от- юоо рицательные числа и манипулиро- 1001 вать ими, поэтому ни одна из схем, юю показанных в табл. 2.7.4, не подхо- 1011 дат. Требуется какое-то компромис- 1100 сное решение, и мы утверждаем, что следующее будет не хуже дру- 11П гих: для тех четырехбитовых кон- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 0 -15 -14 -13 -12 -11 -10 -9 -8 —7 -6 -5 -3 -2 -1
фигураций, в которых бит 3 равен
О, мы используем интерпретацию положительных чисел, для тех, в которых
бит 3 равен 1, используем интерпретацию отрицательных чисел. Результатом
является система чисел со знаком, содержащая как положительные, так и от-
рицательные числа. Эта схема представляется потенциально полезной, посколь-
ку половина четырехбитовых конфигураций теперь интерпретируется как от-
рицательные числа, а другая половина как положительные или 0. Этот ком-
промисс показан в табл. 2.7.5. Несомненно, что эта числовая система также
конечна и также циклична (рис. 2.7.6), хотя в этом случае разрыв непрерыв-
ности в системе происходит между 7 и —8, а не между 15 и 0, как прежде.
Таблица 2.7.5
Двоичное число Интерпретируемое как число со знаком
0000 0
0001 1
0010 2
ООП 3
0100 4
0101 5
оно 6
ош 7
1000 -8
1001 -7
1010 -6
1011 -5
1100 -4
1101 -3
1110 —2
1111 -1
Рис. 2.7.6
25
Очевидно, что в интерпретации четырехбитового слова как числа со зна-
ком самый старший бит, бит 3, приобретает особую важность. Если этот бит
равен 0, то число интерпретируется как положительное или 0, если он равен
1, то число отрицательное. По этой причине самый старший бит называют зна-
ковым битом.
2.8. ДОПОЛНЕНИЕ ДО ЕДИНИЦЫ И ДОПОЛНЕНИЕ ДО ДВУД
Когда знаковый бит четырехбитового числа со знаком равен 0, значение
числа легко вычисляется — мы просто игнорируем знаковый бит и оцениваем
оставшиеся три бита. Таким образом, например, значение 0101 — это просто
значение 101, а именно 5. Однако, если знаковый бит равен 1, ситуация не-
сколько усложняется. Мы не может просто отбросить знаковый бит, оценить
оставшиеся три бита и затем инвертировать результат. Если бы это было так,
то значение 1101, например, было бы отрицательным значением числа 101, а
именно —5. Но в действительности 1101 интерпретируется как — 3,анекак— 5.
Для оценки отрицательного числа было бы достаточно иметь возможность
изменить его знак. Таким образом, если бы мы могли, например, определить,
что отрицательное значение числа 1101 было ООН, то могли бы утверждать,
что значением 1101 является отрицательное 3. Это приводит к более общему
вопросу вычисления отрицательного значения любого числа, а не только та-
кого , знаковый бит которого равен 1.
Ниже приводится интересная схема, которая легко реализуется и вводит
два важных понятия. Рассмотрим четырехбитовое число к (со знаком). При
заданной битовой конфигурации для к мы хотим найти конфигурацию для
числа —к. Рассмотрим (правильное) уравнение — к = 0 — к = (1 — 1) — к =
= — 1 — к + 1 = (— 1 — К) + 1. Из него следует, что для вычисления значения —к
необходимо лишь вычесть к из —1 (т. е. из 1111) и затем прибавить 1 (т. е.
0001). Значимость вычитания из —1 состоит в том, что это вычитание всегда
возможно и никогда не требует заема. Легко можно проверить, что вычита-
ние четырехбитового числа из 1111 (—1) равнозначно инвертированию битов
в этом, четырехбитовом числе, т. е. нули меняются на единицы, а единицы ме-
няются на нули. Такое инвертирование битов в числе к называется дополне-
нием до единицы числа к. Теперь мы можем завершить процесс нахождения
отрицательного значения к, прибавляя единицу к дополнению до единицы
числа к, что дает в результате отрицательное значение к, называемое дополне-
нием до двух числа к. Ниже показан процесс нахождения —к, где к = 1101:
1111 (-1)
— 1101 (вычесть к)
0010 (дополнение до 1 числа к)
+0001 (прибавить 1)
0011 (дополнение до 2 числа к, т. е. —к).
Поскольку мы предположили, что в процессоре имеется соответствующая
схема для выполнения дополнений (дополнений до единицы) и для увеличе-
ния четырехбитовых слов, то процессор может теперь инвертировать числа.
Рассматривая интерпретацию четырехбитовых слов как чисел без знака
(чисел в диапазоне от 0 до 15), мы определили, что арифметическая пробле-
ма возникает, когда 1 прибавляется к 1111, что является результатом пере-
носа из бита 3. В случае чисел со знаком этот перенос не вызывает проблемы,
поскольку прибавление 1 к НИ приводит к 0000,что правильно: (—1) 4- 1 =
= 0. Но здесь появляется новая проблема, а именно, увеличение (прибавление
1) числа 7 (0111). Арифметически это выглядит так:
0111
4-0001
1000
При интерпретации как чисел без знака это сложение утверждает, что 7 4-
f 1 = 8. Но если считать, что числа со знаком, то соответствующее уравнение
принимает вид 7 4-1= —8, показывая, что что-то неверно. Обратите,однако,
внимание, что проблема здесь возникает не в результате переноса из знаково-
го бита; уравнение неправильно из-за переноса в знаковый бит - переноса из
бита 2 в бит 3. В результате увеличения знаковый бит устанавливается, т. е.
становится равным 1, тогда как до увеличения он был равен 0.
2.9. ИНДИКАТОРЫ ПЕРЕНОСА И ПЕРЕПОЛНЕНИЯ
В предыдущих разделах мы исследовали четырехбитовые слова и в резуль-
। ате различных интерпретаций наложили на них две разные числовые систе-
мы — двоичную без знака и двоичную со знаком. Мы увидели, что при неко-
торых обстоятельствах в результате переноса в самый старший бит или из не-
го не все идет так хорошо, как нам бы хотелось с арифметической точки зре-
ния: в некоторых случаях вычисления бывают правильными, а в других не-
правильными. И хотя нельзя удовлетворяться тем, что машина дает результа-
ты сомнительной арифметической правильности, мы обнаружили источник
трудностей — конечность размера слова ЭВМ вместе с нашей интерпретацией
содержимого слова как представления двоичных чисел. В любом случае пере-
носы, приводящие к этим проблемам, не проходят для процессора незаме-
ченными.
В процессоре содержатся два индикатора — индикатор переноса и индика-
тор переполнения, которые могут рассматриваться как однобитовые ’’мини-
слова”. Каждый из них может быть процессором установлен (ему придается
значение 1) или сброшен (придается значение 0). Индикатор переноса обыч-
но ассоциируется с переносом из самого старшего бита, тогда как индикатор
переполнения обычно устанавливается переносами в самый старший бит. Та-
ким образом, можно ожидать, что процессор после завершения операции,
в которой происходит перенос из бита 2 в бит 3, устанавливает индикатор пе-
реполнения. Если такой перенос не случается, то индикатор переполнения
обычно сбрасывается. Индикатор переноса обрабатывается аналогичным об-
разом. Значимость этих индикаторов состоит в том, что после выполнения
операции процессором мы можем их проверить. Если состояние индикаторов
показывает, что возможен неправильный арифметический результат,то могут
быть предприняты соответствующие действия, чтобы исправить ситуацию.
Как проверяются эти индикаторы, мы увидим несколько позже. Пока же
нам достаточно уверенности в том, что, хотя некоторые арифметические ре-
зультаты могут быть ’’неправильными”, мы, по крайней мере, в состоянии
контролировать то, что раньше представлялось хаотической ситуацией.
26
27
Способ, которым процессор управляет этими индикаторами, не настоль-
ко прост, как это предполагалось в предыдущем абзаце. У читателя может
создаться впечатление, что если при завершении некоторой операции проис-
ходит перенос из бита 3, то индикатор переноса всегда устанавливается, в
противном случае он сбрасывается и что при переносе в бит 3 индикатор пе-
реполнения будет устанавливаться, а в противном случае сбрасываться. Фак-
тически же способ обработки процессором этих индикаторов зависит от вы-
полняемой операции, а не просто от того, были или нет переносы. В некото-
рых случаях они будут трактоваться так, как описано выше. В других случа-
ях один или другой индикатор может быть установлен или сброшен незави-
симо от того, происходил ли в действительности перенос в бит 3 или из него.
И все же есть еще случаи, когда индикаторы просто остаются без изменения.
Например, когда ЦП PDP-11 увеличивает слово, он устанвливает индикатор
переполнения, если происходит перенос в знаковый бит (бит 3); если нет, то
индикатор сбрасывается. Но индикатор переноса остается без изменения не-
зависимо от того, был или не был перенос из бита 3. Таким образом, в этом
случае переносы из самого старшего бита не обнаруживаются.
Невозможно дать общие правила для условий, при которых эти индикато-
ры устанавливаются, сбрасываются или остаются без изменения. Действия
ЦП зависят от выполняемой операции. Достаточно будет сказать, что аппара-
тура разрабатывается таким образом, чтобы эти индикаторы давали нам по-
лезную информацию в целом ряде обстоятельств.
2.10. ЕЩЕ НЕМНОГО О ЧЕТЫРЕХБИТОВОЙ АРИФМЕТИКЕ
В этом разделе мы познакомимся еще с двумя примерами манипуляции
со словами — сложением и вычитанием. Опять полагаем, что в процессоре
имеется необходимая схема для выполнения этих операций. Сначала рассмот-
рим проблему простого сложения:
ООП
+0010
0101
Если считать это сложением без знака, оно представляет 3 + 2 = 5. В случае со
знаком сложение становится таким: (+3) + (+2) =+5. Результат в обоих слу-
чаях правильный, и мы замечаем, что ни переноса из бита 3, ни переноса из
бита 2 в бит 3 не происходит.
Теперь рассмотрим следующее:
0101
+0011
1000
В обоих случаях, со знаком и без знака, это есть 5 + 3. В случае без знака ре-
зультат равен 8, что правильно. В случае со знаком результат равен —8, что
арифметически неверно. Но зту неправильность мы в состоянии определить,
поскольку происходит перенос в бит 3.
. Сложение
1101
+0101
0010
28
неправильно в случае без знака: 13 + 5 = 2. Но в случае со знаком оно верно:
—3 + 5 = 2. Обратите внимание, что происходят оба переноса - в самый стар-
ший бит и из него.
И, наконец, рассмотрим:
1010
+ 1001
ООП
Здесь как интерпретация без знака, так и интерпретация со знаком дают не-
правильный результат: 10+9 = Зи—6+ (—7) = 3 соответственно. Обратите
внимание, что перенос из бита 3 происходит, но переноса в бит 3 нет.
Теперь сделаем некоторое обобщение, которое читателю предлагается про-
верить с помощью упражнений. Если четырехбитовые слова, подлежащие сло-
жению, интерпретируются как представляющие числа без знака, то результат
сложения будет арифметически правильным при отсутствии переноса из са-
мого старшего бита. Если слова интерпретируются как числа со знаком, то
результат будет арифметически правильным при условии, что: а) не происхо-
дит переноса ни в самый старший бит, ни из него; б) имеют место оба этих
переноса.
Теперь, когда мы знаем условия, при которых сложение дает в результа-
те правильный и неправильный ответы, можно задаться вопросом, как в этом
случае процессор управляет индикаторами переноса и переполнения. Если
происходит перенос из бита 3, то индикатор переноса устанавливается, если
нет, то индикатор переноса сбрасывается. Таким образом, состояние индика-
тора переноса (сброшенное или установленное) после завершения операции
сложения показывает соответственно правильность или неправильность
операции сложения без учета знака. Индикатор переполнения будет устанав-
ливаться, если два складываемых числа имели один и тот же знак, но резуль-
тат сложения получается с противоположным знаком; в противном случае
он сбрасывается. (По-другому можно было бы сказать, что индикатор пере-
полнения устанавливается тогда и только тогда, когда происходит точно
один из переносов в знаковый разряд или из него) . Таким образом, состоя-
ние индикатора переполнения (сброшенное или установленное) может ис-
пользоваться для определения соответственно правильности или неправиль-
ности сложения с учетом знака. Сказанное является примером такого спосо-
ба установки процессором этих индикаторов, который (хотя он, вероятно,
не совсем простой), дает максимум информации. (Следует заметить, что по-
ведение процессора в отношении этих индикаторов описано применительно
специально к ЭВМ PDP-11, хотя действия этого процессора не являются уни-
кальными — другие процессоры ведут себя в случае сложения так же.)
Вычитание будем трактовать несколько более поверхностно. Рассмотрим
следующую задачу вычитания:
1001
-ООП
ОНО
Вычисления здесь совершенно простые, хотя заметим, что требуется заем из
бита 3 в бит 2 и в конце концов в бит 1. В случае без знака результат есть 9 -
29
— 3 = 6,что правильно. Однако в случае со знаком он будет (—7) — 3 = 6, что,
конечно, неверно.
Теперь рассмотрим:
ООН
-1001
?010
Здесь возникает затруднение, поскольку вычитание в бите 3 не может быть
выполнено без заема и кажется, что заем сделать неоткуда. Справиться с этой
ситуацией можно двумя способами. Пусть считаем сначала, что уменьшаемое
равно 10011, а не 011 (в этой маленькой хитрости предполагается, что умень-
шаемое фактически имеет длину 5 бит, но это не столь возмутительно, как
может показаться). Вычитание теперь становится таким:
10011
- 1001
1010
с заемом из несуществующего бита 4. Результат 1010 является правильным,
что легко проверить путем сложения 1001 + 1010 = ООН.
Другой способ трактовки вычитания, при котором не встречается отмечен-
ная выше проблема и который фактически используется в ЭВМ PDP-11 при
выполнении этой операции, состоит в перезаписи вычитания типа А - В как
сложения А + (—В) . Таким образом, необходимо только инвертировать вы-
читаемое и сложить его с уменьшаемым. Поскольку отрицательное значение
от 1001 есть 0111, приведенный пример, становится таким:
ООН ООП
— 1001 тквивалентно +Qin
1010
Обратите внимание, что в случае без знака вычитание будет давать результат
3 — 9 = 10, а в случае со знаком 3 — (—7) = -6, т. е. в обоих случаях арифме-
тически неправильный.
В качестве последнего примера рассмотрим
1101 1101
1Q10 эквивалентно +0Н0
ООП
В случаях со знаком и без знака —13 — 10 = 3 и (—3) — (—6) = 3 соответ-
ственно — ответ арифметически правилен.
Читатель не удивится, обнаружив, что правильность или неправильность
результатов вычитания и сложения зависит от того, происходили или нет пе-
реносы в самый старший бит или из него. Каковы эти условия, ему предлага-
ется определить в упражнениях 2.10.6 и 2.10.7. Чтобы понять, как процессор
устанавливает индикаторы переноса и переполнения, мы должны помнить,
что он выполняет вычитание как сложение: А - В = А + (—В). Если это сло-
жение приводит в результате к переносу из самого старшего бита, то индика-
тор переноса сбрасывается, в противном случае or устанавливается. Обратите
внимание, что здесь картина в точности обратна поведению индикатора пере-
носа при сложении. Когда индикатор переноса установлен, подразумевается,
so
что для выполнения вычитания требуется заем из несуществующего бита 4.
Индикатор переполнения устанавливается, если оба участвующие в вычита-
нии числа имеют противоположные знаки (имеют противоположные биты 3),
а результат вычитания имеет тот же самый знак (бит 3), что и вычитаемое, в
противном случае индикатор переполнения сбрасывается.
Небольшое экспериментирование обнаруживает следующие факты. Как и
в случае сложения, состояние индикатора переноса (сброшен или установ-
лен) показывает соответственно на правильность или неправильность вычита-
ния без учета знака. Аналогично сброшенный или установленный индикатор
переполнения сигнализирует соответственно о правильности или неправиль-
ности вычитания с учетом знака. И, Наконец, вспоминая опять, что процес-
сор выполняет вычитание как сложение, можно утверждать, что индикатор
переноса устанавливается тогда и только тогда, когда нет переноса из самого
старшего бита (бита 3), а индикатор переполнения устанавливается тогда и
только тогда, когда был в точности один перенос в знаковый бит или из него.
2.11. УПРАЖНЕНИЯ
2.4.1. Покажите, что элемент памяти, составленный из независимых ячеек памяти,мо-
жет предполагать 2” различных конфигураций. Как можно интерпретировать этот ре-
зультат, если п = О?
2.5.1. Опишите число 100 в терминах пальцев и наборов пальцев.
2.5.2. Преобразуйте каждое из следующих представлений чисел (при указанном осно-
вании системы счисления) в представление при основании, указанном в правой части:
а) 1425 = й 10 е) 100110010, = — 8
«л 2 1 А —
О/ ХпХде 5 Ж) О X О 8 4
пЧ 147 — -аЛ 1О1Э —
ХЧХ8 10 37 X Z X В
тЛ 147 — иЧ Д 1 А —
В И) 3XUg - 2
д) 1001100Ю2 = 4 к) 12124 = 2
2.5.3. Преобразуйте каждое из следующих десятичных представлений чисел в двоич-
ное (по основанию 2) :
а) 20 г) 32 ж) 1024
б) 5 Д) 65 з) 2047
в) 15 е) 127 и) 129
2.5.4. Преобразуйте каждое из следующих двоичных (по иснованию 2) представле-
ний чисел в десятичное:
а) 101 г) 100001 ж) 101010
б) 1001 Д) НИИ з) 100000
в) 1101 е) 100100 и) 111011
2.53. Преобразуйте каждое из десятичных чисел упражнения 233 в его восьмерич-
ное (по основанию 8) представление.
23.6. Преобразуйте каждое из двоичных чисел упражнения 23.4 в его восьмеричное
(по основанию 8) представление.
23.7. Преобразуйте каждое из следующих восьмеричных (по основанию 8) представ-
лений чисел в шестнадцатеричное (по основанию 16) :
а) 102235 в) 47 д) 70452
б) 16 г) 77777 е) 177776
31
2.5.8. Преобразуйте каждое из следующих шестнадцатеричных (по основанию 16)
представлений чисел в восьмеричное (по основанию 8) :
a) 1F в) Fl a)FFFF
б) Е2 г) A BCD е) 1FFE
2.5.9. Выполните указанные арифметические операции по заданным основаниям
(проверьте результаты с помощью преобразования всех чисел к представлению по осно-
ванию 10). а) 1267 4- 6627 г) 772048 - 16558 ж) 1044б х ЗО5б
б) 1268 4* 662 8 д) 1011102 - 11101г з) 10112 X 11012
в) 1011012 4- 110112 е) 63468 х 447 8 и) 5728 24
2.5.10. Сложение двух чисел 132 и 33 по основанию 4 будет выполняться так, как по-
казано ниже (при использовании стандартного алгоритма сложения), где числа вверху
(курсивные) показывают переносы:
11
132
+ 33
231
Покажите в этом конкретном случае, что алгоритм сложения есть просто результат
сложения двух полиномов: 1 ° х2 + 3 ° х1 + 2 ° х° и 3 ’ х1 + 3 • х° (где х = 4) с соответ-
ствующей группировкой и приведением коэффициентов.
2.5.11. Покажите, что число цифр, необходимых для представления числа по основа-
нию п, может быть в к раз больше числа цифр, необходимых при основании п^. (Таким
образом, например, для двоичного представления числа может потребоваться в 3 раза
больше цифр, чем нужно при восьмеричном представлении.)
2.7.1. Определите наибольшее (десятичное) целое число, которое может помещаться
в 8-битовых, 12-битовых, 16-битовых, 24-битовых, 32-битовых и 36-битовых словах.
2.7.2. Покажите, что если слово памяти состоит из п бит, то наибольшее число, кото-
рое может в нем храниться, равно 2м - 1.
2.8.1. Покажите, что если слово памяти состоит только из одного бита, то увеличение
этого слова будет равноценно его дополнению, т. е будет реверсировать его состояние.
2.8.2. Покажите, что, если слово памяти состоит только из двух битов, то увеличение
этого слова будет равноценно дополнению бита 0 и, если результат в бите 0 был 0, также
дополнению бита 1 (см. упражнение 2.8.1) .
2.8.3. Покажите, что результаты упражнений 2.8.1 и 2.8.2 могут быть обобщены на че-
тырехбитовые слова (фактически на «-битовые) . Чтобы увеличить такое слово, начинай-
те с бита 0 и дополняйте каждый бит до тех пор, пока результатом последнего дополне-
ния не станет 1.
2.8.4. Найдите дополнение до единицы и дополнение до двух для каждой из следую-
щих четырехбитовых конфигураций слова:
а) 1010 б) 1111 в) 0000 г) 1000 д) 0111.
2.8.5. Обычно единственным числом, равным своему собственному отрицательному
значению, является число 0. Если четырехбитовые слова интерпретируются как пред-
ставления двоичных чисел со знаком, то покажите, что -0 = 0, но также, что - (-8) =
= -8.
2.8.6. При построении системы чисел со знаком для четырех битовых слов мы выбра-
ли схему 0, 1, . . . , 7, -8, -7, . . ., -1, в которой самый старший бит (бит 3) использо-
вался для определения того, является ли число отрицательным или неотрицательным.
В тексте, было сказано, что эта схема, ввиду того что она разделяет отрицательные и не-
отрицательные числа на равные группы, является приемлемой, как любая другая такая
схема. Покажите, что фактически она является единственной приемлемой схемой в том
смысле, что в любой другой схеме перенос в самый старший бит не мог бы использовать-
ся для определения правильности арифметических результатов.
2.10.1. Выполните следующие операции над заданными четырехбитовыми словами.
В каждом случае укажите, происходил ли перенос в бит 3 или из него, как устанавлива-
ются индикаторы переноса и переполнения и ’’правильно” ли выполнялась арифмети-
ческая операция, если ее рассматривать с учетом или без учета знака:
а) 0101 в) 1101 д) 1110
+0011 -000 -1000
б) 1010 г) 1010
+1010
+0011
е) ООН
-1010
2.10.2. Если четырехбитовые слова интерпретируются как числа без знака, то пока-
жите, что результат сложения двух таких слов будет арифметически правильным тогда
и только тогда, когда не будет переноса из бита 3.
2.10.3. Если четырехбитовые слова интерпретируются как числа со знаком, то пока-
жите, что результат сложения двух положительных чисел будет арифметически правиль-
ным тогда и только тогда, когда не будет переноса в бит 3.
2.10.4. Если четырехбитовые слова интерпретируются как числа со знаком, покаж-
те, что результат сложения двух отрицательных чисел будет арифметически правильным
тогда и только тогда, когда будет происходить перенос в бит 3. Обратите внимание , что
перенос из бита 3 в этом случае всегда происходит.
2.10.5. Если четырехбитовые слова интерпретируются как числа со знаком, покажи-
те, что результат сложения отрицательного и положительного чисел всегда будет пра-
вильным, а перенос в знаковый бит будет происходить тогда и только тогда, когда бу-
дет также происходить перенос из знакового бита.
2.10.6. Если четырехбитовые слова интерпретируются как числа без знака, найдите
условия переноса (из бита 3 или в него), при которых результат вычитания будет ариф-
метически правильным.
2.10.7. Если четырехбитовые слова интерпретируются как числа со знаком, то найди-
те условия переноса (из знакового бита или в него), при которых результат вычитания
KxFTfa'I' --------
будет арифметически правильным.
ГЛАВА 3. ЛОГИКА
3.1. ВЫСКАЗЫВАНИЯ И СОЮЗЫ
Мы считаем, что основная задача ЭВМ состоите обработке информации, в
которую обычно включаются операции над данными. Когда думаешь об опе-
рациях, выполняемых вычислительной машиной, на ум приходят основные
арифметические действия — сложение, умножение и т. п. В предыдущей гла-
ве мы явно полагали, что наша машина с четырехбитовым словом обладает
некоторыми из этих возможностей. Однако эти традиционные арифметичес-
кие операции несколько сложнее ряда других, выполняемых электронными
| Зак. 2212 33
32
схемами в ЭВМ. В данной главе мы изучим фундаментальные блоки, исполь-
зуемые для построения таких более сложных вычислительных функций. Ис-
тория происхождения этих фундаментальных блоков может показаться чи-
тателю интересной и удивительной.
Когда мы говорим или пишем по-русски1, мы редко делаем это с помо-
щью простых предложений типа ’’подлежащее — сказуемое — дополнение”;
зачастую такие простые предложения объединяются с помощью специальных
слов, называемых союзами. Примеры союзов — И, ИЛИ, ЕСЛИ НЕ, ЕСЛИ . . .
. . . ТО, НО. Мы изучим свойства лишь нескольких из них. При этом мы не
станем пытаться использовать формальный подход, известный как пропози-
циональная логика, поскольку такой уровень формализма мало чем может
нам помочь в понимании работы ЭВМ. Более неформальное изложение по-
зволит нам довольно быстро продвинуться к конечной цели: построению
схем для выполнения простых, но важных логических операций.
Под предложением мы будем понимать грамматически правильное (син-
таксически приемлемое) предложение в русском языке. Высказывание -
это декларативное предложение, истинность или ложность которого может
быть определена некоторым однозначным способом. Таким образом, такое
предложение, как ’’Сегодня вторник” является высказыванием, поскольку
это декларативное предложение и его истинность или ложность можно опре-
делить. Какое истинностное значение (исЮина или ложь) мы присваиваем это-
му предложению, не столь существенно, нас здесь интересует просто то, что
такое присвоение возможно. Поскольку присваивание истинностного значе-
ния предложению может приводить к путанице, имеет смысл рассмотреть
два примера предложений, не являющихся высказываниями в смысле данно-
го выше описания.
Рассмотрим вначале предложение ’’Следите за своим весом”. Конечно, это
предложение в русском языке, но оно не более истинно, чем ложно. Истин-
ность или ложность — это характеристики, которые, как показывает наш
опыт, не могут быть присвоены такому предложению. Под вопросом стоит
также декларативность этого предложения; В качестве второго примера рас-
смотрим классическое предложение ’’Это предложение ложное”. Без сомне-
ния это русское предложение, и оно декларативное, поскольку в нем делает-
ся утверждение. Однако если мы попытаемся присвоить истинностное значе-
ние, то столкнемся с трудностями. Так, если мы говорим, что ’’Это предло-
жение ложное” есть истина, то это истинное предложение утверждает свою
собственную ложность. И обратно, присвоение этому предложению значения
ложь подразумевает, что ложным будет, что ’’Это предложение ложное”, т. е.
само предложение есть истина. Таким образом, мы не в состоянии однознач-
но утверждать истинность или ложность этого предложения. Поэтому предло-
жения, подобные этому, мы исключаем из множества высказываний. Впредь
нас будет интересовать не то, какую информацию может переносить выска-
зывание, а тот факт, что высказывание может принимать в точности одно из
значений - истина или ложь, причем однозначным способом, ибо в против-
ном случае этот способ будет неопределенным.
1 В оригинале здесь и далее в этой главе речь идет об английском языке. - Прим, перев.
34
Напоминаем, что наш непосредственный интерес направлен к составным
предложениям, состоящим из простых предложений и объединяющих их со-
юзов. Не видно причин, почему само составное преложение не могло бы яв-
ляться высказыванием, и мы будем придерживаться именно этой идеи. Пред-
положим, что составное предложение образовано двумя высказываниями,
каждому из которых мы можем присвоить истинностное значение. Если пред-
полагается, что это составное предложение есть высказывание, то мы долж-
ны быть в состоянии присвоить и ему истинностное значение. Поэтому оче-
видный вопрос состоит в том, как следует присваивать истинностное значе-
ние составному предложению, образованному двумя высказываниями и со-
юзом. В известной мере такое присвоение может быть сделано произвольно,
но русский язык диктует, что истинностное значение составного высказыва-
ния зависит от значений составляющих его высказываний.
Чтобы прояснить этот вопрос, рассмотрим общепринятый союз И, а в каче-
стве иллюстративного примера — составное предложение ’’Сегодя пятница, И
идет дождь”. Очевидно, что это предложение составлено из двух отдельных
предложений ’’Сегодня пятница” и ’’Идет дождь”, каждое из которых являет-
ся высказыванием. Наш языковый опыт подсказывает заключение, что это
составное высказывание будет истинным, если действительно сегодя пятница
и, кроме того, идет дождь, т. е. составное предложение истинно при условии,
что истинным является каждый из его компонентов. С другой стороны, если
сегодня не пятница, то составное предложение ложно, независимо от состоя-
ния погоды. И наоборот, в солнечную погоду это предложение будет ложным
для любого дня недели.
Из этого примера следует формальный способ присвоения истинностного
значения конъюнкции — составному предложению, образованному простыми
высказываниями, объединенными с помощью И. Такое присвоение зависит
от присвоения значений индивидуальным высказываниям: конъюнкция ис-
тинна, если оба индивидуальные высказывания истинны, в противном случае
конъюнкция ложна. Эти результаты сведены в табл. 3.1.1, где Р использует-
ся для представления первого высказывания (’’Сегодня пятница”), a Q ис-
пользуется для представления второго высказывания (’’Идет дождь”) .Бук-
вами И и Л соответственно обозначены значения истина и ложь.
Обратите внимание, что в таблицу мы включили все возможные комбина-
ции истинностных значений для Р и Q. Когда интерес прежде всего представ-
ляет структура предложений, а не их значения, союз И обычно заменяется
символом А. Тогда табл. 3.1.1 принимает вид табл. 3.1.2.
а б л и ц а Р 3.1.1 Q Таблица 3.1.2 рА<?
РИ£? р Q
И и И и и и
И л Л и л л
Л и Л л и л
л л Л л л л
Еще один обычно используемый союз — это ИЛИ. Предложение, образо-
ванное в результате объединения двух высказываний с помощью ИЛИ,назы-
35
вается дизъюнкцией двух высказываний. В табл. 3.1.3 представлено опреде-
ление союза ИЛИ, для которого используется логический символ V-
Если взять в качестве примера два простых высказывания, использован-
ных выше (Р: Сегодня пятница, Q'. Идет дождь) , то читатель, вероятно, со-
гласится, что наше определение союза ИЛИ соответствует повседневному
употреблению. Но в русском языке слово ИЛИ иногда используется несколь-
ко иначе. Специального слова для этого типа дизъюнкции нет, о таком моди-
фицированном применении мы узнаем из контекста. Рассмотрим, например,
дизъюнкцию ”На каникулах мы собираемся в горы ИЛИ мы собираемся на
берег моря”. Здесь подразумевается редко добавляемая фраза ”но не в оба
места”. Это совершенно другое использование союза ИЛИ, при котором ис-
ключается возможность того, что оба простых высказывания истинны. Это
ИСКЛЮЧАЮЩЕЕ ИЛИ, для которого используется символ У и для опреде-
ления которого требуется модификация первой строки таблицы, в которой
определяется ИЛИ (табл. 3.1.4) .
Таблица 3.1.3
Р Q
Т а б л и ц а 3.1.4
Р Q
p\LQ
И И И
или
ЛИИ
л л л
ИИ л
или
ли и
л л л
Сделанных определений союзов вполне достаточно для наших целей, мы
даже продвинулись несколько дальше, чем необходимо. Но нам нужна еще
одна довольно простая вещь, являющаяся не союзом, но модификатором,
которая не объединяет пары высказываний, а воздействует на отдельные
высказывания. Этот модификатор называется НЕ, для него используется
символ ~. Будучи примененным к высказыванию, он придает этому выска-
зыванию противоположное истинностное значение (табл. 3.15).
Высказывания могут быть более сложными, чем встречавшиеся до сих
пор простые Р KQ, но разобраться с ними можно довольно легко. Рассмот-
рим, например,высказывание ~ (Р V Q) • Для определения истинностного зна-
чения этого высказывания при заданных значениях индивидуальных выска-
зываний Р и Q сначала необходимо оценить только частьР\/ Q конструкции и
затем инвертировать (операция НЕ) результат (табл. 3.1.6).
В качестве другого примера рассмотрим высказывание А Опять
значения этого высказывания мы можем определить исходя из значений для
Р и Q, оценивая выражение по частям (табл. 3.1.7).
Таблица' 3.1.5 Р ~Р Таблица 3.1.6 P4Q ~(PW) Таблица 3.1.7 -Q 4»A -Q
Р Q Р Q ~Р
И Л И и и л И И л л л
Л и И л и л И л л и л
Л и и л л и и л л
Л л л и л л и и и
36
У этих двух последних примеров есть одно интересное свойство, которое
будет иметь для нас некоторые последствия. Обратите внимание, что для
каждого конкретного присвоения истинностных значений простым высказы-
ванием Р и Q оба составные высказывания имеют одно и то же значение. Если
два высказывания и S2 образуются (с помощью союзов и модификато-
ров) из простых высказываний Р, Q, R, ... и если при каждом присвоении
значений высказываниям Р, Q, R, . . . два высказывания 5'1 и52 принимают
одно и то же значение, то говорят, что Si и S2 эквивалентны, что записывает-
ся так: = S2. Если применить это определение к вышеприведенным приме-
рам, то получаем ~(Р V Q) = ~Р Л ~2- Ряд других примеров читатель най-
дет в упражнениях.
3.2. ПЕРЕКЛЮЧАТЕЛЬНЫЕ СХЕМЫ
Переключатель — это устройство, которое может находиться в одном из
двух состояний: закрытом или открытом. Когда переключатель закрыт, че-
рез него может протекать ток, когда открыт, ток проходить не может. Пере-
ключатели будем изображать так, как показано на рис. 3.2.1. Поскольку за-
крытый переключатель трудно отличить от простого проводника, мы впредь
будем изображать переключатели в открытом положении, а состояние их
определять исходя из текста.
f Выход
Закрытый
Открытый
Рис. 3.2.1
Измерительный
\У / прибор
Рис. 3.2.2
Источник напряжения
Простейшая схема, содержащая переключатель, показана на рис. 3.2.2.
Мы полагаем, что используется некоторый источник напряжения величиной
I В, хотя фактическое напряжение не имеет значения. Напряжение на выходе
схемы может быть измерено с помощью измерительного прибора (вольтмет-
ра) . Если переключатель открыт, то ток не течет и выходное напряжение рав-
но О В. Но когда переключатель закрыт, через измерительный прибор проте-
кает ток, и прибор показывает напряжение 1 В. Эти результаты сведены в
• абл. 3.2.3.
Поскольку напряжение на выходе схемы непосредственно соответствует
положению переключателя, будем говорить, что закрытый переключатель
имеет значение 1, а открытый переключатель имеет значение 0. Тогда табли-
ца может быть переписана в виде табл. 3.2.4.
Таблица 3.2.3 Таблица 3.2.4
Переключатель Выход Переключатель Выход
Закрытый 1 11
Открытый 0 0 0
Поскольку такая ситуация слишком проста, чтобы представлять какой-ли-
бо практический интерес, рассмотрим более сложную схему, состоящую из
37
в
Источник напряжения
—f Выход
1
Ф Измерительный
прибор
Таблица 3.2.6
Переключатель
А В Выход
1 1 1
1 0 о
О 1 о
ООО
Рис. 3.2.5
+ 11-------10
о Выход
Таблица 3.2.8
Переключатель
А о Выход
О
Источник напряжения
1 1 1
1 о 1
О 1 1
О 0 о
Рис. 3.2.7
Та б л и ц а 3.2.10
Переключатель
АВС Выход
+ 1 О
о Выход
Источник напряжения
Рис. 3.2.9
111 1
110 1
10 1 1
10 0 0
0 11 1
0 10 0
0 0 1 1
0 0 0 0
двух переключателей, соединенных последовательно (рис. 3.25) . Поскольку
теперь есть два переключателя, Л и В, которые, как мы полагаем, могут уста-
навливаться независимо друг от друга, то в выходной таблице требуется че-
тыре строки для обозначения различных установок переключателей. Опять мы
используем 1 для закрытого переключатели и 0 для открытого (табл. 3.2.6).
Еще одна схема с двумя переключателями, соединенными параллельно,
показана на рис. 3.2.7. Здесь, как и в дальнейшем, мы не показываем измери-
тельный прибор, поскольку полагаем, что напряжение на выходе может быть
измерено тем или иным способом. Читателю не составит труда проверить, что
выходная таблица будет такой, как табл. 3.2.8.
Эти два примера (схемы с последовательным и параллельным соедине-
нием переключателей) представляют единственно возможные схемы, содер-
жащие два переключателя. Конечно, схема может содержать и больше двух
переключателей. Пример такой схемы показан на рис. 3.2.9. Читателю реко-
мендуется проверить, что эта схема может быть описана табл. 3.2.10.
38
3.3. ЕЩЕ РАЗ О ВЫСКАЗЫВАНИЯХ С ИЗМЕНЕНИЕМ НОТАЦИИ
Ъ табл. 33.1 воспроизведено определение из разд 3.1 для союзов И, ИЛИ,
и ИСКЛЮЧАЮЩЕЕ ИЛИ, а также для модификатора НЕ со следующим изме-
нением нотации: буквы И и Л (истина и ложь) заменены соответственно еди-
ницами и нулями.
Таблица 3.3.1
Р Q р /\Q р Q PX/Q
1 1 1 1 1 1
1 0 0 1 0 1
а) 0 1 0 б) о 1 1
0 0 0 0 0 0
Р Q p~\LQ р
1 1 0 1 0
в) 1 0 1 г) 0 1
0 1 i
0 0 0
Сравнение табл. 3.3.1,а и 3.2.6 показывает, что, если отвлечься от назва-
ний элементов, эти таблицы идентичны. Таким образом, схема с последова-
тельными переключателями может быть интерпретирована как физическое
представление союза И при условии, что закрытый переключатель интерпре-
тируется как представляющий истинное высказывание, а открытый — как
представляющий ложное высказывание, единичный выход — как истинная
конъюнкция, нулевой — как ложная конъюнкция. В равной степени справед-
ливо и обратное утверждение: схема с последовательными переключателями
может быть представлена союзом логических высказываний И. Аналогично,
из табл. 33.1,6 и 3.2‘В можно увидеть, что схема с параллельными переклю-
чателями и дизъюнкция ИЛИ — зто просто разные представления одной и той
же абстрактной концепции. Представления с помощью переключательных
схем союза ИСКЛЮЧАЮЩЕЕ ИЛИ и модификатора НЕ не столь просты, по-
этому мы отложим дальнейшее обсуждение до следующего раздела. (Заме-
тим также, что логическим эквивалентом схемы на рис. 3.2.9 является выска-
зывание (А Л В) V С.)
Некоторые последствия такого двойного представления этих концепций
мы рассмотрим в следующем разделе. В упражнениях мы разовьем некото-
рые другие последствия, которые не столь непосредственно сопряжены с ис-
пользованием этих идей в ЭВМ. В любом случае читатель должен быть уверен в
том, что, располагая соответствующими интерпретациями, мы теперь облада-
ем методом вычисления логических высказываний физически — путем по-
строения электрических схем, представляющих определенные логические вы-
сказывания.
3.4. РЕЛЕ И ВЕНТИЛИ
Теперь мы находимся на прямом пути к принципиальной цели этой гла-
вы — конструированию компонентов, способных ’’выполнять логические опе-
рации”, поведение которых в определенном смысле совпадает с поведением
логических союзов и модификатора НЕ. Как мы увидим, эти компоненты об-
39
Подвижный контакт
О
Неподвижный контакт
Рис. 3.4.1
Г
1
А
В
Рис. 3.4.2
разуют основные блоки, из которых строится аппаратура ЭВМ. Однако здесь
мы сталкиваемся с некоторой проблемой. Рассматривая аппаратуру ЭВМ (на-
пример, устройство, состоящее из ячеек памяти) , мы настаивали на том, что
эти компоненты должны быть способны реагировать на электрический ток;
и с этой точки зрения рассмотренные в данной главе переключатели не явля-
ются удовлетворительными, поскольку они управляются вручную, как обыч-
ные выключатели освещения. Поэтому необходимо их несколько модифици-
ровать. К счастью, это не трудно сделать.
Рассмотрим железный сердечник с намотанным вокруг него проводни-
ком. Когда по проводнику протекает ток, сердечник намагничивается (рис.
3.4.1,а). При отсутствии тока сердечник не намагничен. Предположим, что
мы модифицировали этот так называемый электромагнит, поместив вблизи
от одного из концов железного сердечника стальную пружинящую полоску
с контактом (рис. 3.4.1,6). Когда по проводнику протекает ток, пружиня-
щая полоска притягивается к намагниченному сердечнику, и между подвиж-
ной полоской и неподвижной возникает контакт. Когда тока нет, контакт от-
сутствует. Такие контакты действуют как переключатели, управляемые то-
ком в обмотке. Подобное приспособление называется реле. Таким образом,
переключения, позволившие нам продвинуться вперед в двух предыдущих
разделах, могут выполняться с помощью подачи или отключения тока, и мы
можем теперь заново построить переключательные схемы, представляющие
И и ИЛИ с помощью таких реле.
Линии, помеченные на рис. 3.4.2 буквами Л и В, называются входами пере-
ключательной схемы. Если напряжение на входе А равно О В, то ток через ле-
40
А В
Рис. 3.4.3
вое реле не протекает, оно не намагничивается и, следовательно, контакт раз-
мыкается (переключатель, помеченный 5^ , открыт). Если на входе А есть на-
пряжение 1 В, то через реле протекает ток и переключатель SA закрыт. Пере-
ключатель Sg управляется аналогично с помощью подачи или отключения на-
пряжения на входе В. Нетрудно определить, что на выходе этой схемы будет
1 тогда и только тогда, когда на обоих входах А и В будет 1, т. е. эта схема
представляет операцию И входов А и В. Если провода в месте пересечения
показаны крестом без точки, то они не соединяются. Соединяемые провода
обозначаются точкой в месте их пересечения. Значение штриховой линии, в
которую заключена большая часть рисунка, будет вкратце пояснено ниже.
Аналогично читатель может проверить, что на рис. 3.4.3 показана схема,
представляющая логическое ИЛИ входов А и В. Таким образом, мы видим,
что можно разработать схемы с двумя входами, Л и В, на каждом из кото-
рых может быть 0 или 1, и одним выходом, на котором может быть 0 или 1
в зависимости от состояния входов. В первом случае на выходе будет Л /\В,
во втором случае Л V В.
Здесь нас интересует только существование таких схем, а не то, как они
строятся. В наших примерах эти схемы построены из реле, но реле уже мно-
гие годы не используются для этих целей в вычислительных машинах. Обыч-
но такие схемы строятся на транзисторах, которые также способны работать
как переключатели, но намного быстрее, чем довольно медленные реле. Для
нас важно, что такие схемы существуют.
Таким образом, схемы, содержащиеся внутри штриховых линий на рис.
3.4.2 и 3.4.3, могут быть сведены к ’’черным ящикам”, что позволяет скон-
центрировать внимание на их входах и выходах (рис. 3.4.4). Конечно, как
нам известно, такие черные ящики нуждаются в источниках напряжения, ко-
торые мы не показываем. Такие скемы обычно называют вентилями. Их гра-
фические обозначения показаны на рис. 3.4.5.
41
Входы
Выход -
А КВ
Входы
Выход =
Д V в
а)
Рис. 3.4.4
Входы
Вентиль И
Входы
Выход =
А V В
Вентиль ИЛИ
Рис. 3.4.5
Выход = ~ А
Выход = ~Д
Вход А
Выход ~А
Вход А
Инвертор (вентиль НЕ)
Рис. 3.4.7
Рис. 3.4.9
°"* —вмлд;
Вентиль ИСКЛЮЧАЮЩЕЕ ИЛИ
Рис. 3.4.8
1
О
О
1
Реле может быть также использовано для физического представления ло-
гического модификатора НЕ. Для этого необходимо только, чтобы контакты
реле были нормально закрыты (т. е. контакты закрываются при отсутствии
тока и открываются при его наличии). Как легко можно проверить, на рис.
3.4.6 показана такая схема. Мы опять сведем эту схему к черному ящику, ко-
торый называется инвертором (иногда его называют вентилем НЕ); его гра-
фическое обозначение показано на рис. 3.4.7. Обратите внимание, что этот
вентиль обладает одним входом.
42
Теперь мы, наконец, можем заняться союзом ИСКЛЮЧАЮЩЕЕ ИЛИ, кото-
рому до сих пор внимания не уделяли. Графическое обозначение вентиля
ИСКЛЮЧАЮЩЕЕ ИЛИ показано на рис. 3.4.8. Теперь сравнительно легко
можно построить такой вентиль с помощью вентилей И, ИЛИ и инверторов.
На рис. 3.4.9 приведены две схемы, каждая из которых представляет ИСКЛЮ-
ЧАЮЩЕЕ ИЛИ, что читателю рекомендуется проверить.
3.5. ДВЕ АРИФМЕТИЧЕСКИЕ СХЕМЫ
В этой главе мы вкратце обсудили логику высказываний, которая приве-
ла нас к переключательным схемам, релейным схемам и вентилям — устрой-
ствам, дающим физическое представление логических союзов. Но читатель
может поинтересоваться, какое отношение все это имеет к вычислительным
машинам и, в частности, к машине с четырехбитовым словом, рассмотренной
в предыдущей главе.
Мы утверждали, что такие физические представления образуют основные
блоки, из которых строится обрабатывающая аппаратура ЭВМ, и теперь на-
стало время проверить это. Вспомним, что в разд. 2.7 мы заявили о возмож-
ности построения схемы ЭВМ, способной выполнять увеличение (на 1) четы-
рехбитового слова. Схема на рис. 3.5.1 действительно увеличивает такое сло-
во. Здесь x3x2*i*o обозначает четырехбитовое слово, подлежащее увеличе-
нию, ау3у2У1Уо — результирующее четырехбитовое слово (x3x2XjX0 + 1).
Поскольку каждое интерпретируется как 0 или 1, его можно использовать
в качестве входа для вентилей ИСКЛЮЧАЮЩЕЕ ИЛИ и И. На выходе венти-
ля ИСКЛЮЧАЮЩЕЕ ИЛИ опять будет 0 или 1, которые мы интерпретируем
как значение ук. Обратите внимание, что в схеме имеется также фиксирован-
ный вход со значением 1.
Читателю рекомендуется проанализировать эту простую схему с целью
проверки того, что она действительно выполняет увеличение. Проводя этот
анализ, можно увидеть, что вентили ИСКЛЮЧАЮЩЕЕ ИЛИ выполняют факти-
ческое увеличение, тогда как вентили И сохраняют информацию о переносах
из каждого бита в более старший бит. Эта схема обеспечивает также ’’вылав-
ливание” любого переноса в бит 3, что позволяет осуществить соответству-
ющую установку индикатора переполнения, которая обычно производится
при увеличении.
В разд. 2.10 мы предполагали, что существует схема для сложения четы-
рехбитовых слов. Для этого требуется несколько более сложная схема, чем
для увеличения, поскольку теперь имеются два входа в схему, соответству-
ющие двум словам, подлежащим сложению. На рис. 3.5.2 показан фрагмент
схемы для выполнения сложения четырехбитовых слов (или, что все равно,
для выполнения сложения «-битовых слов) . Показана только схема, необхо-
димая для сложения Л-го бита одного слова (x3x2XjX0) с к-м битом второго
слова (У3У2У1У0) Для получения к-то бита результата (z3z2zlz0).no линии
Сг поступает перенос из предыдущего (более младшего) бита. Таким об-
разом, состояние Ci равно либо 0, либо 1 в соответствии с тем, происхо-
дил или нет перенос при сложении (к — 1)-х битов. Аналогично, линия Со
представляет перенос в следующий более старший битв результате сложения
к-х битов. Таким образом, для формирования полной схемы сложения че-
тырехбйтовых слов необходимо только связать вместе четыре таких схемы.
43
На уровне бита 0 будет достаточно установить состояние Ci равным 0, хотя
существуют более простые способы обработки этого специального случая.
Читателю рекомендуется проверить, что эта схема будет работать так, как
описано, а также определить, как могут быть включены в полную схему
сложения четырехбитовых слов индикаторы переноса и переполнения.
О схемах, описанных в этом разделе, мы утверждаем лишь то, что они
выполняют желательную задачу. Возможны эквивалентные схемы, более эф-
фективные и состоящие из меньшего числа компонентов. И хотя любая ЭВМ
должна быть в состоянии осуществлять увеличение и сложение, в конкрет-
ных машинах для выполнения этих функций могут использоваться схемы,
отличные от показанных и зависящие от общей архитектуры машины. Такие
схемы являются частью арифметикологического устройства (АЛУ) централь-
ного процессора. В этом устройстве содержатся также схемы (зачастую назы-
ваемые логикой) для выполнения других фундаментальных арифметических
операций. В нашем изложении мы сознательно игнорируем ряд проблем, в
основном инженерной природы, возникающих при фактической реализации
операций над битами и четырехбитовыми словами, поскольку их изучение от-
влекало бы нас от основной цели — ознакомления с тем, что представляют
собой вычислительные машины, что они делают и, лишь в общих словах, как
они этого добиваются.
3.6. УПРАЖНЕНИЯ
3.1.1. С помощью таблиц и истинностных значений дайте определение каждому из
следующих союзов:
а) ЕСЛИ ... ТО б) НО в) ЕСЛИ НЕ
3.1.2. Пусть Р, Q и R - высказывания. Определите, что будут означать выражения
Р Л Q Д R и Р \/ Q \/ R. (Указание. Покажите, что (Р Д Q) Д Р s Р Д (Q Д R) .)
3.1.3. а) Покажите, что Р Д (Q V Л) — N Q) V U* Л Ю
б) Покажите, что Р\/ (С Л Ю s (РУ Q) К (?У R)
в) Покажите, что "-Р V ~(Р Л С) •
3.1.4. Покажите, что Ру Q = (РУ Q) Д (~Р V ~Q) и что РУ Q = (РУ Q) Д ~(Р Д Q).
3.13. Покажите, что Р Д Q — К О = ~{-Р У ~Q).
44
3.1.6. Определите союз ИМПЛИКАЦИЯ, обозначаемый символом =», как показано в
табл. 3.6.1.
Таблица 3.6.1
Р Q P~Q
И И И
ИЛЛ
ЛИИ
л л и
а) Покажите, что Р =» Q = ~(Р/\ ~(?) s ~Р\/Q.
б) Покажите, что значением высказывания (Р Д (Р=»0)) =*Q всегда будет истина,
независимо от значений Р и Q. Такое высказывание называется тавтологией.
в) Каждый из союзов, определенных в тексте, обладает свойством Р A Q = Q А Р, где
Д — любой из символов Д , V или V- Покажите, что ИМПЛИКАЦИЯ (=*) не обладает
этим свойством.
3.3.1. Покажите, что переключательная схема на рис. 3.2.9 — это физическая реализа-
ция логического высказывания (А Д В) V С.
3.4.1. Покажите, что если один из входов вентиля ИЛИ равен 0, то выход этого вен-
тиля равен другому его входу, т. е. в этом случае вентиль по отношению к другому вхо-
ду работает как простой проводник.
3.4.2. Покажите, что если один из входов вентиля И есть 1, то выход этого вентиля
равен другому входу.
3.4.3. Покажите, что две схемы, определяющие вентиль ИСКЛЮЧАЮЩЕЕ ИЛИ (см.
рис. 3.4.9) , эквивалентны в том смысле, что при заданных входах А и В выходы обеих
схем одинаковы. Покажите это следующим способом:
а) определяя выход каждой схемы для всех возможных комбинаций входов;
б) используя результаты упражнения 3.1.4, покажите, что схема на рис. 3.4.9,а есть
физическая реализация логического высказывания (А V В) Д (~А V ~^)> тогда как
схема на рис. 3.4.9, б реализует логическое высказывание (А V В) Л ~ И Д В).
3.4.4. Покажите, что выход схемы, изображенной на рис. 3.6.2, всегда будет равен 1,
независимо от состояния входов А и В\
а) путем нахождения выхода для всех возможных комбинаций входов;
б) учитывая результаты упражнений 3.1.6,д и б.
3.45. Написав логическое высказывание, эквивалентное схеме, показанной на рис.
3.6.3, и выполнив его преобразования, покажите, что эта схема может быть сведена к
эквивалентной схеме, показанной на рис. 3.6.4.
Рис. 3.6.4
45
3.4.6. Покажите, что если на одном из входов вентиля ИСКЛЮЧАЮЩЕЕ ИЛИ удер-
живается 1, то этот вентиль работает как инвертор для другого входа.
34.7. Рассмотрим новый тип вентиля, называемый просто Х-вентил ем, которому со-
ответствует логический символ X. Его определение и графическое изображение приведе-
ны на рис. 3.6.5.
а) Покажите, что если вход В удерживается равным 0, то Х-вентиль работает как ин-
вертор для входаА.т. е.так, как показано на рис. 3.6.6.
б) Покажите, что Х-вентиль может использоваться для построения эквивалента вен-
тиля ИЛИ, а именно так, как показано на рис. 3.6.7.
в) Покажите, что Х-вентиль может использоваться для построения эквивалента вен-
тиля И (используйте тот факт, что Х-вентили могут применяться для построения инвер-
торов и вентилей ИЛИ, а также результаты упражнения 3.1.5).
г) Покажите, что все вентили, рассмотренные в разд. 3.4, могут быть построены из
Х-вентилей, т. е. что один этот примитивный компонент может использоваться для кон-
струирования любой схемы разд. 3.5. (Фактически Х-вентиль - это не что иное, как вен-
тиль ИЛИ, за которым следует инвертор, т. е. Х-вентиль эквивалентен схеме, показанной
на рис. 3.6.8. Обычно применяемое для этого вентиля графическое обозначение показа-
но на рис. 3.6.9; зачастую такой вентиль называется вентилем ИЛИ-НЕ.)
Входы
А В Выход =<4X5
1 1 . 1 0 0 1 0 0 Рис. 3.6.5 А— 0— Рис. 3.6.6 А в— 0 Рис. 3.6.7 А—К В—) У 1/^ Рис. 3.6.8 /3 /2 Л Л ( X, *2 > Рис. 3.6.10 46 ° Г\ п г, А — Выход = 0 Входь| дхв 1 Х-Вентиль эк Бивалентно А £^>о—— А X X. 1 эквивалентно 1 >— А \ "X (AVS) >>-—-~(AVS) в—/ У Рис. 3.6.9 У1 /о f I «—О q Рис. 3.6.11
3.5.1. Проверьте, что схема на рис. 3.5.1 увеличивает четырехбитовое слово.
35.2. Разработайте схему для уменьшения четырехбитового слова на 1 (Указание.
В каждом бите вход для вентиля ИСКЛЮЧАЮЩЕЕ ИЛИ - это выход и один из входов
предыдущего младшего вентиля.)
3.5.3. Как реагирует на четырехбитовое слово схема, показанная на рис. 3.6.10? То
есть, каково отношение между выходом Т3Т2Т1Т0 и входом х3х2х1х0? (Указание.
Четырехбитовые слова нужно рассматривать как числа со знаком).
3.5.4. Используйте фрагмент схемы на рис. 3.5.2 для построения полной схемы сло-
жения четырехбитовых слов, уделяя особое внимание индикаторам переноса и перепол-
нения и выполнению сложения нулевых битов. Индикаторы переноса и переполнения
должны устанавливаться так, как описано в разд. 2.10.
3.5.5. Рассмотрим схему, показанную на рис. 3.6.11.
а) Покажите, что если S = 0 и /? = 1, то £? = 0 и (2 = 1.
б) При состоянии, описанном в предыдущем пункте (т. е. S = 0 и R = 1), покажите,
что установка R равным 0 не изменяет значения выходов Q и Q .
в) При состоянии, описанном в предыдущем пункте, покажите, что установка S рав-
ным 1 реверсирует состояние выходов Q и Q. _
г) И, наконец, покажите, что возврат S в 0 не влияет на выходы Q и Q.
Эта схема называется защелкой. Поскольку основной выход этой схемы Q может
устанавливаться равным 0 или 1 соответствующими установками R и S, и поскольку на
этот выход не влияет возврат в 0 входов R и S’, то защелка является потенциальной
ячейкой памяти. И действительно, защелки используются в качестве устройств памяти,
когда желательна или обязательна высокая скорость установки состояния ячейки памяти.
ГЛАВА 4.16-БИТОВЫЕ СЛОВА И АДРЕСАЦИЯ ПАМЯТИ
4.1. ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ С 16-БИТОВЫМ СЛОВОМ
В гл. 2 мы упоминали, что изученные там четырехбитовые слова слишком
малы, чтобы ограниченный диапазон чисел, которые эти слова способны удер-
живать, представлял какой-то практический интерес: от 0 до 15 (без знака)
или от —8 до +7 (со знаком). И все же машина с четырехбитовым словом не-
плохо послужила нам, позволив ввести ряд понятий: числовые системы ЭВМ,
знаковый бит, различные переносы, арифметические операции и т. п. Теперь
мы перейдем к такому размеру слова, который ближе к практическим по-
требностям и позволяет сохранять намного больший диапазон реально ис-
пользуемых чисел. По ряду причин мы выбираем 16-битовое слово. Во-пер-
вых, оно достаточно велико для довольно широкого диапазона чисел и в
то же время не громоздко. Во-вторых, 16-битовое слово стало в определен-
ной степени промышленным стандартом, поскольку используется в широ-
ком ряде мини-ЭВМ. И машина PDP-11, представляющая для нас особый ин-
терес, также работает с 16-битовым словом.
О самих словах, их числовых системах или арифметическом поведении
изучать что-либо новое не приходится. Если отвлечься от факта, что они на
12 бит длиннее, чем известные нам четырехбитовые слова, то поведение их
идентично. Эта аналогия показана в табл. 4.1.1. Индикаторы переноса и пере-
полнения устанавливаются и сбрасываются в результате арифметических
операций аналогично тому, как это делается в случае четырехбитового слова.
47
Таблица 4.1.1.
Особенность Четырехбитовое слово 16-битовое слово
Названия битов Бит 3.... , бит 0 Бит 15,...,бит 0
Знаковый бит Бит 3 Бит 15
Числовая От 0 до 15 (без учета знака) От 0 до 65535 (без учета знака)
система От -8 до +7 (с учетом знака) От -32768 до +32767 (с учетом
знака)
Переносы Перенос из бита 2 в бит 3 Перенос из бита 14 в бит 15
Перенос из бита 3 Перенос из бита 15
4.2. ЧИСЛОВЫЕ ПРЕДСТАВЛЕНИЯ
Несмотря на то что различные понятия, введенные в случае четырехбито-
вого слова, без существенных изменений переносятся на случай 16-битового
слова, при работе с этим большим размером слова мы встречаемся с пробле-
мой, которая не была очевидна (а фактически, и не существовала в действи-
тельности) при обсуждении четырехбитовых слов. Это проблема представле-
ния. Так, мы можем сказать, что четырехбитовое слово содержит, например,
двоичное число 1011, и при интерпретации как со знаком, так и без учета зна-
ка, легко можем понять, какое число представлено. Но рассмотрим теперь
16-битовое число 1001110001001101. Очень трудно почувствовать, что это за
число, хотя мы можем сказать, что при интерпретации с учетом знака оно от-
рицательно. Это слишком длинная строка двоичных цифр, чтобы с ее помо-
щью письменно выражать содержимое слова памяти. Малообнадеживающей
она представляется и в случае устного произнесения:
о дин-но ль-ноль-один-один-один-ноль-ноль-ноль-один-ноль-ноль-
один-один-ноль-один
Поэтому необходимы какие-то средства для сжатия записи до меньшего
числа цифр. Это можно сделать следующим образом: разбивая двоичное пред-
ставление на группы по две цифры в каждой и рассматривая каждую такую
пару цифр как двоичное представление числа по основанию 4, мы можем за-
писать 16-битовое число как число по основанию 4, которое будет содержать
вполовину меньше цифр. Например,
10 01 И 00 01 00 11 01
2 1 3 0 1 0 3 1
Таким образом, 10011100010011012 = 213010314. Мы видим, что формиро-
вание числа по основанию 4 осуществляется вполне легко (путем группиров-
ки числа по основанию 2 в наборы по две цифры и вычисления этих цифр), и
так же легко можно восстановить число по основанию 2 по данному пред-
ставлению по основанию 4.
Легко можно показать, что такая манипуляция допустима. В нашем при-
мере:
1001110001001101 = 1 • 215 + 0 • 214 + 0 • 213 + 1 • 212 + 1 • 211 + 1 • 210 +
+ 0 • 29 + 0 • 28 + 0 • 27 + 1 • 26 + 0 • 25 + 0 • 24 +
+ 1 • 23 + 1 • 22 + 0 • 21 + 1 • 2° = (1 • 2 + 0) • 214 +
+ (0*2+1) • 212 + (1 • 2 + 1) • 210 + (0 • 2 + 0) • 28 +
+ (0 • 2 + 1) • 26 + (0 • 2 + 0) • 24 + (1 • 2 + 1) • 22 +
+ (0 • 2 + 1) • 2° =
= 2 • 47 + 1 • 46 + 3 • 45 + 0 • 44 + 1 • 43 + 0 • 42 +
48 +3 • 41 + 1 • 4°-.
Это, несомненно, прогресс, поскольку удалось уменьшить наполовину чис-
ло цифр, необходимых для представления. Однако запись из восьми цифр
все-таки не очень удобна, и поэтому мы делаем дальнейшую попытку сжатия,
на этот раз группируя по три двоичных цифры и получая восьмеричное (по
основанию 8) представление числа.
1 001 110 001 001 101
116 115
То есть 10011100010011012 = 1161158. Обратите внимание, что здесь есть не-
которое неудобство, хотя, к счастью, оно не существенно. При группировке
двоичных цифр в наборы по три самый старший бит (бит 15) остается в оди-
ночестве, поскольку 16 на 3 не делится. Но в действительности здесь никакой
трудности нет, поскольку это просто означает, что восьмеричное представле-
ние 16-битового слова всегда начинается с 0 или 1 в соответствии с тем, равен
ли бит 15 нулю или единице. Здесь читатель вновь может убедиться, насколь-
ко легко одно из представлений (двоичное или восьмеричное) может быть
преобразовано в другое.
Уменьшив число цифр в представлении до шести, мы теперь сделаем еще
одну попытку улучшения, на этот раз группируя по четыре двоичных цифры.
Поскольку четыре двоичные цифры могут представлять числа в диапазоне от
0 до 15, то представление будет происходить по основанию 16, т. е. это будет
шестнадцатеричное основание системы счисления, рассмотренное вкратце в
разд. 25.
1001 1100 0100 1101
9 С 4 D
Таким образом, 10011100010011012 = 9C4D16. Любые дальнейшие попытки
сжатия представления 16-битового двоичного числа не кажутся перспектив-
ными. При разбивке представления на группы по пять цифр в каждой все-та-
ки потребуется по четыре цифры в представлении (по основанию 32), и само
такое основание системы счисления слишком велико, чтобы с ним было удоб-
но обращаться. При основании 16 для представления 16-битовых чисел требу-
ется четыре цифры, что дает весьма компактную запись, к которой мы стре-
мимся. Недостатком основания 16 является использование ’’цифр” — А, В, С,
D, Е, F, к которым приходится привыкать заново. При основании 8 пред-
ставление несколько длиннее (шесть цифр), но обладает преимуществом ис-
пользования только привычных цифр 0, 1, ... 7. Выбор между восьмерич-
ным (по основанию 8) или шестнадцатеричным (по основанию 16) представ-
лениями может быть сделан здесь с помощью жребия.
4.3. ПОЛУСЛОВА (БАЙТЫ)
В разд. 4.1 мы решили иметь дело с 16 битовыми словами по ряду причин,
одной из которых было то, что такое слово достаточно велико, чтобы в нем
могли храниться имеющие практическое значение числа. Однако в некоторых
приложениях такой размер слова оказывается слишком уж щедрым. Напри-
мер, в гл. 10 мы увидим, что в большей части вычислений, ежедневно выпол-
няемых на ЭВМ, фактически приходится обрабатывать числа в диапазоне от
0 до 127 (в десятичном виде) . Таким образом, поскольку объем оператив-
49
ной памяти ограничен (хотя у некоторых ЭВМ он может быть весьма боль-
шим) , приходится искать средства для экономии этого ресурса при обработ-
ке небольших чисел, намного меньших, чем те, которые могут быть помеще-
ны в 16-битовое слово. Поэтому каждое слово мы делим на полуслова, на-
зываемые байтами, и сможем убедиться в дальнейшем, что в ряде случаев
достаточно манипулировать отдельными байтами, а не целыми словами, ко-
торым они принадлежат.
15 8 7 0 (битовая позиция)
х.........х х..........х
Старший байт Младший байт
Старший байт иногда называют левым байтом или передним байтом. Анало-
гично, младший байт называют правым байтом или задним байтом.
Так же как полные слова могут представляться в восьмеричной или шест-
надцатеричной форме, каждый отдельный байт может быть разбит на группы
по 3 или 4 бита для сжатия его числового представления. Таким образом, два
байта 10011100 и 01001101 16-битового слова 1001110001001101 могут быть
представлены в шестнадцатеричной форме
1011 1100 и 0100 1101
9 С 4 D
и в восьмеричной форме
10 011 100 и 01 001 101
2 3 4 1 1 5
Обратите внимание, что шестнадцатеричным представлением полного слова
будет 9C4D, его старшего байта - 9С, а младшего байта - 4D. Иными слова-
ми, представление старшего байта состоит из первых двух цифр представле-
ния полного слова, а представление младшего байта — из последних двух
цифр. Но ситуация с восьмеричным представлением намного менее приятная.
Восьмеричное представление полного слова - это 116115, его старшего бай-
та — 234, а младшего байта — 115. То есть, формируя восьмеричное представ-
ление старшего байта, мы не можем просто отделить первые три цифры из
представления полного слова. И хотя в этом примере представление младше-
го байта случайно совпадает с последними тремя цифрами представления пол-
ного слова, небольшие эксперименты показывают, что это не общий случай.
Достаточно, например, рассмотреть слово,восьмеричное представление кото-
рого равно 162524.
До сих пор у нас не было особых причин, чтобы отдать предпочтение шест-
надцатеричному или восьмеричному представлению 16-битового слова. Вы-
шеупомянутое неудобство восьмеричного представления склоняет чашу ве-
сов в пользу шестнадцатеричного, особенно если мы намереваемся иметь де-
ло с отдельными байтами. Для многих ЭВМ шестнадцатеричная нотация пред-
ставляется наиболее естественной. Олнако архитектура ЭВМ PDP-11 облада-
ет некоторой особенностью, обсуждаемой в гл. 7, которая диктует нам исполь-
зовать в остальной части книги восьмеричное представление, даже несмотря
на дополнительные вычисления при формировании байтов из слов или слов
из байтов. Впредь, если явно не указано иное, все числовые представления, за
исключением очевидно десятичных или двоичных, предполагаются восьме-
ричными.
Прежде чем закончить этот раздел,заметим, что, разделив слова на байты,
или полуслова, мы тем самым создали другую структуру памяти с соответ-
ствующими ей числовыми и арифметическими понятиями. Если речь идет об
отдельных байтах как противоположности полным словам, то к ним отно-
сятся все идеи, которые мы обсуждали по отношению к четырехбитовым и
16-битовым словам. В частности, восьмибитовый байт может интерпретиро-
ваться как представляющий число без знака (в диапазоне от 0 до 255) или
число со знаком (между —128 и +127) . В случае с учетом знака самый стар-
ший бит будет знаковым битом. Это или бит 15, или бит 7, в зависимости от
того, старший или младший байт полного слова рассматривается. Примени-
мы также понятия переносов в самый старший бит и из него совместно с воз-
можными эффектами по отношению к индикаторам переноса и переполнения.
4.4. АДРЕСАЦИЯ ПАМЯТИ
Рассматривая слова памяти (четырехбитовые и 16-битовые) и байты в
этой и предыдущих главах, мы также отмечали, что оперативная память ти-
пичной ЭВМ состоит из тысяч или сотен тысяч элементов памяти. Читатель,
вероятно, подумал о том, что если мы намереваемся увеличить содержимое
слова или сложить содержимое двух слов, то должны располагать возмож-
ностью указать, какое слово или какие слова должны подвергаться воздей-
ствию. А если, как было заявлено, операции могут выполняться над отдель-
ными байтами, то нужен способ однозначной идентификации самих байтов.
Такую схему идентификации можно легко получить, ассоциировав уникаль-
ное число, называемое адресом байта, с каждым байтом оперативной памяти.
Фрагмент памяти с байтовой адресацией показан на рис. 4.4.1.
Обратите внимание, что для этих адресов мы использовали восьмеричное
представление и просто взяли в качестве адресов байтов последовательные
целые числа, начиная от 0. Такие адреса очень похожи на номера домов, по-
скольку они используются для локализации и идентификации конкретных
байтов в оперативной памяти. И подобно номерам домов они являются чис-
лами без знака, ведь не имеет смысла говорить, например, об отрицательном
адресе 002045. Байты, адреса которых отличаются на 1, например 000202 и
000203 или 104553 и 104554, называются соседними байтами (здесь речь
идет о соседстве в смысле схемы адресации; два таких байта как элементы
памяти из восьми ячеек не обязательно физически следуют друг за другом в
оперативной памяти).
Если должно быть увеличено содержимое байта памяти, то такое увеличе-
ние происходит в ЦП, конкретнее, в арифметическом устройстве ЦП. Чтобы
увеличить содержимое байта, ЦП должен получить это содержимое из опера-
тивной памяти, что он и делает, определяя адрес байта. Где в оперативной па-
мяти расположен этот конкретный байт, т. е. каков его адрес, для ЦП значе-
ния не имеет, поскольку он может осуществлять доступ к содержимому од-
ного байта так же легко, как к содержимому любого другого. Из-за этой осо-
бенности оперативную память часто называют запоминающим устройством с
произвольной выборкой (ЗУПВ).
51
50
Адреса старших байтов Слова памяти Адреса младших байтов Адреса слов
000001 — 000000 000000
000003 000002 000002
000005 000004 000004
000007 000006 000006
000011 000010 000010
000013 000012 000012
-- X
002043 002042 002042
002045 002044 002044
002047 002046 002046
002051 002050 002050
Рис. 4.4.1 L— Рис. 4.4.2
Разработав схему идентификации (адресации) для отдельных байтов, мы
теперь переключим внимание на полные слова. Поскольку слова также могут
обрабатываться процессором (при сложениях, увеличениях и т. п.), то они
тоже должны быть однозначно идентифицируемы. Схема адресации слов очень
проста: числовой адрес слова равен адресу младшего байта этого слова (ср.
рис. 4.4.2 и рис. 4.4.1) .
По поводу адресации слов можно сделать два очевидных, но важных вы-
вода. Во-первых, адреса слов всегда четные. Во-вторых, схема байтово-по-
словной адресации неоднозначна в следующем смысле. Пусть термин ячейка
памяти используется для обозначения или байта, или слова. Рассмотрим опе-
рацию ’’Увеличить содержимое ячейки памяти с адресом 002045” Здесь не
возникает вопроса о том, что это означает, поскольку адрес нечетный, и обра-
щение, следовательно, выполняется к байту с адресом 002045, а именно, к
старшему байту слова с адресом 002044. Но рассмотрим теперь операцию
’’Увеличить содержимое ячейки памяти с адресом 002050” Здесь есть дву-
смысленность, поскольку неясно, должно ли увеличиваться содержимое бай-
та с адресом 002050 или слова с адресом 002050. Такая двусмысленность
действительно существует, и из предыдущих примеров видно, что она непри-
емлема. Однако эту проблему можно решать не на уровне схемы адресации,
которая в этом смысле неоднозначна, а на уровне самих операций. Поэтому в
действительности нет такой операции, как ’’Увеличить содержимое ячейки па-
мяти с адресом .. . а вместо этого имеются две разные подобные операции:
’’Увеличить содержимое байта с адресом ...” и ’’Увеличить содержимое слова
с адресом . . . В остальной части книги мы иногда будем использовать тер-
мин ячейка памяти для обозначения физического слова или байта. Во всех
52
таких случаях будет ясно, к чему это относится (к байту или слову) либо из
контекста, либо из явного указания в тексте.
И последний комментарий к этому примеру. Чтобы читатель не подумал,
будто увеличение содержимого слова эквивалентно увеличению содержимо-
го младшего байта этого слова (в этом случае вышеупомянутая неоднознач-
ность не имела бы последствий) , рассмотрим следующее слово:
10011001 11111111
Если увеличивается слово, то результат будет 10011010 00000000. Если уве-
личивается младший байт слова, то результатом явится 10011001 00000000,
поскольку при увеличении содержимого младшего байта он рассматривается
как полный элемент памяти и переноса из бита 7 в бит 8 не происходит.
Именно в этом единственном случае результаты арифметически различаются.
Однако есть и еще один случай, в котором увеличение слова и увеличение
младшего байта слова могут приводить к разным последствиям (см. упраж-
нение 4.4.3).
4.5. УПРАЖНЕНИЯ
4.1.1. Проверьте, что числовая система с 16-битовым словом охватывает диапазон
(в десятичном виде) от 0 до 65 535 (без учета знака) или от —32 768 до +32 767 (с уче-
том знака).
4.2.1. Преобразуйте каждое из следующих чисел по основанию 2 в его эквивалентное
представление по основанию 4:
а) 0100001011111011 в) 1010010001000011
б) 1011011101111011 г)1111111111111111
4.2.2. Преобразуйте каждое из следующих чисел по основанию 4 в его эквивалентное
представление nd основанию 2:
а) 30303010 в) 12321232
б) 01020302 г) 13333331
4.2.3. Покажите, что в общем случае число по основанию 2, состоящее из 16 цифр,
может быуь представлено как число по основанию 4 в соответствии с процедурой, опи-
санной в разд. 4.2, доказав, что
где а. = 0 или 1.
4.2.4. Покажите, что в общем случае число по основанию 2, состоящее из 16 цифр,
может быть представлено как число по основанию 8 в соответствии с процедурой, опи-
санной в разд. 4.2, доказав, что
15 14 4 2 .
S а. • 21 =а15 • 2IS + S а. • 2l =ais • 8s + S (S . • 2Л) • 8J,
i=0 1 i=o 1 h'k-,
где a. = 0 или 1.
4.2.5. Сформулируйте и докажите результат, соответствующий упражнениям 4.2.3 и
4.2.4 для преобразования числа по основанию 2 в его шестнадцатеричное представление.
4.2.6. Преобразуйте каждое из чисел по основанию 2 из упражнения 4.2.1 в его:
а) восьмеричное представление;
б) шестнадцатеричное представление.
4.2.7. Преобразуйте каждое из следующих восьмеричных чисел в его эквивалентное
16-цифровое двоичное представление:
а) 102343 в) 177276 д) 111111
б) 003004 г) 043022 е) 171203
53
4.2.8. Преобразуйте каждое из следующих шестнадцатеричных чисел в его эквива-
лентное 16-цифровое двоичное представление:
а) 84F3 в) FEBE д) 9249
б) 0604 г) 4612 е) F283
4.2.9. Найдите дополнение до единицы и дополнение до двух (отрицательное значе-
ние) для каждого из 16-битовых слов, представленных в восьмеричном виде в упражне-
нии 4.2.7 и в шестнадцатеричном виде в упражнении 4.2.8. Приведите результаты соот-
ветственно в восьмеричном и шестнадцатеричном виде (Указание. Для нахождения до-
полнения до единицы используйте схему из разд. 2.7 в восьмеричном и шестнадцатерич-
ном виде и затем прибавьте 1 для нахождения дополнения до двух).
4.2.10. Выполните указанные арифметические операции над 16-битовыми словами,
приведенными ниже в восьмеричном представлении, указывая переносы в самый стар-
ший бит и из него и определяя состояние индикаторов переноса и переполнения. В каж-
дом случае укажите, является ли результат арифметически правильным при интерпрета-
ции без учета знака и с учетом знака:
а) 102306 в) 162662 д) 016302
+004035 +145606 -060033
б) 062404 г) 102306 е) 162662
+016333 -004035 -145606
4.2.11. Повторите упражнение 4.2.10 с числами, представленными в шестнадцатерич-
ном виде:
а) 010Е в) 72ЛЗ д) Е4Е5
+0В5С +0F2X -8020
б) 8283 г) 03Л4 е) 8020
+F877 -0259 -Е4Е5
4.3.1. Найдите содержимое (в восьмеричном виде) старшего и младшего байтов
16-битовых слов, восьмеричные представления которых даны ниже:
а) 102703 в) 001001 Д) 177777
б) 000101 г) 101401 е) 077672
4.3.2. Напишите восьмеричное представление 16-битовых слов, старшие и младшие
байты которых (в восьмеричном виде) даны ниже:
а) старший байт = ООО, младший байт = 206;
б) старший байт = 306, младший байт = 000;
в) старший байт — 277, младший байт -= 277;
г) старший байт - 303, младший байт = 303;
д) старший байт : 200, младший байт = 002;
е) старший байт =177, младший байт = 076.
4.3.3. Каковы дополнения до единицы и до двух для каждого из следующих байтов
(выраженных в восьмеричной записи) ?
а) 177 б) 377 в) 000 г) 276
4.4.1. Укажите адреса байтов, соседних относительно каждого из байтов, адреса ко-
торых даны ниже. В каждом случае определите, какой соседний байт принадлежит к то-
му же самому слову, что и указанный байт, и укажите адрес этого слова.
а) 072303 в) 000000
б) 001002 г) 077777
54
4.4.2. Адресом байта может быть любое число (без знака), которое может быть пред-
ставлено в 16-битовом слове:
а) Каков наибольший (в десятичном виде) байтовый адрес?
б) Сколько (в десятичном виде) существует таких байтовых адресов?
в) Каков наибольший (в десятичном виде) адрес слова?
г) Сколько (в десятичном виде) существует таких пословных адресов?
4.4.3. Рассмотрите 16-битовое слово 0010010101111111, содержащееся в ячейке па-
мяти с адресом 026442, и команды ’’Увеличить содержимое слова с адресом 026442” и
’’Увеличить содержимое байта с адресом 026442”. В каждом случае результирующее сло-
во есть 0010010110000000, и все же эти два увеличения ведут себя несколько по-разно-
му. Почему? (Указание. Рассмотрите состояние индикатора переполнения.)
ГЛАВА 5. ХРАНИМЫЕ ИНСТРУКЦИИ И ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР
5.1. КОНЦЕПЦИЯ ХРАНИМЫХ ИНСТРУКЦИЙ
В предыдущих главах мы говорили об операциях над словами и байтами, а
в гл. 1 было указано, что выполнение таких операций происходит в ЦП. Но
ЦП не выполняет операции (увеличение, сложение и т. п.) в случайном поряд-
ке над случайными словами и байтами. Подобно другим машинам, чтобы
быть полезным, он должен быть управляемым, т. е. выполнять лишь те опера-
ции, которые ему предписаны. Такое условие приводит к ряду вопросов, на
которые мы ответим в этой и последующих главах:
1. Что представляет собой операция ЭВМ?
2. Как операция выполняется!
3. Какие операции способны выполнять вычислительные машины?
4. Как центральному процессору отдается инструкция (команда) выпол-
нять одну из его операций и из чего такая инструкция состоит!
5. Что является источником таких инструкций? То есть, как процессор по-
лучает те инструкции, которые он должен выполнять?
В настоящий момент мы не можем исчерпывающе ответить на первый во-
прос. Пока достаточно считать, что операция ЭВМ — это некоторый тип (обыч-
но арифметической) манипуляции словом или байтом.
На второй вопрос ответ, по крайней мере частично, уже был дан. В гл. 3
мы познакомились с двумя примерами схем, одна из которых предназначена
для увеличения (Четырехбитовых) слов, а другая является фрагментом схе-
мы для выполнения сложения. Таким образом, на этот вопрос можно отве-
тить, что операции выполняются электронным способом с помощью логики
(логической схемы) ЦП. Существует, конечно, много таких операций, кото-
рые мы не обсуждаем, но в примерах гл. 3 читателю было сказано о возмож-
ности разработки схем, необходимых для выполнения всех задач, которые
по нашему желанию процессор был бы способен выполнять. И глубже разра-
батывать эту идею нам не нужно.
Ответ на третий вопрос о возможных операциях ЭВМ зависит от конкрет-
ной машины. Операции, которые может выполнять ЭВМ PDP-11, представля-
ющая для нас специальный интерес, описаны в приложении А. Однако было
бы преждевременным начинать изучение всех этих операций в данный мо-
мент, поскольку требуется немалая подготовительная работа, прежде чем
55
большинство из этих операций обретут для нас смысл. Пока же, если читатель
представляет себе такие операции как сложение, вычитание, уменьшение и
т. п., он не заблуждается, хотя существуют другие стандартные операции, не
столь очевидные. Например, большинство процессоров может выполнять по-
битовые опёрации И и ИЛИ над двумя машинными словами, что напоминает
логические операции из гл. 3. Важное значение имеют также операции наподо-
бие сдвига каждого бита слова или байта на одну позицию влево или вправо.
Ответ на первую часть четвертого вопроса о том, каким образом ЦП полу-
чает инструкцию о выполнении операции, мы временно отложим, хотя позна-
комимся с тем, из чего инструкция состоит. Обычно недостаточно сообщить
процессору о том, выполнение какой операции желательно, — требуется до-
полнительная информация. Так, хотя увеличение — это допустимая операция
ЭВМ, едва ли она может служить инструкцией, поскольку ЦП должен быть
также осведомлен о том, какое слово или байт подлежит увеличению. Поэто-
му пока будем считать, что инструкция ЭВМ — это спецификация операции
ЦП вместе с любой дополнительной информацией, необходимой для того,
чтобы операция была осмысленной; обычно эта информация задается в фор-
мате адресов слов или байтов, над которыми предстоит выполнение операции.
Пятым вопросом мы займемся вкратце позднее в этой и следующих гла-
вах. Пока же временно обратим наше внимание на широко распространенные
электронные карманные калькуляторы.
Большинство карманных калькуляторов обладают следующими возмож-
ностями (хотя многие из них значительно сложнее, чем здесь описано) : чис-
ла могут вводиться с помощью десятиклавишной клавиатуры и отображать-
ся на дисплее на передней панели калькулятора; калькуляторы могут выпол-
нять четыре стандартные арифметические операции; большинство калькуля-
торов способно сохранять числа и промежуточные результаты в каком-то ти-
пе памяти, хотя объем этой памяти обычно весьма мал и ограничен одним-
двумя или, вероятно, полудюжиной чисел. Тем не менее эти сохраненные (за-
писанные) числа могут быть вызваны (прочитаны) и отображены или исполь-
зованы в других операциях.
Но чем же тогда отличается карманный калькулятор от ЭВМ? Разумеется,
арифметические способности ЭВМ могут не ограничиваться простым сложени-
ем, вычитанием ит. п., а оперативная память может иметь в тысячи раз больший
объем. Кроме того, ЭВМ обычно обладает значительно более развитыми сред-
ствами ввода и вывода — такими, как терминал, печатающие устройства и
магнитные ленты. Но это все различия больше количественные, чем по суще-
ству. Мы пока ничего не сказали об ЭВМ такого, что неприменимо было бы к
карманным калькуляторам. Однако есть одна особенность современных ЭВМ,
которая делает их чем-то большим, чем просто ’’большой” карманный каль-
кулятор и к этой особенности относится источник команд.
Проанализируем, как простое сложение 2+3 выполняется на карманном
калькуляторе. На большинстве калькуляторов это делается с помощью нажа-
тия клавиши с цифрой 2, затем нажатия клавиши со знаком плюс, затем на-
жатия клавиши с цифрой 3. Результат 5 появляется на дисплее калькулятора
при нажатии клавиши =. Таким образом, используется следующая последова-
тельность команд:
1. Ввести в память число 2.
56
2. Прибавить к этому числу следующее число, которое будет введено.
3. Ввести число 3 и выполнить сложение, определенное на шаге 2.
4. Отобразить результат сложения.
Нас в меньшей степени интересуют сами команды, но больше то, как каль-
кулятор получает эти команды, т. е. источник команд. Очевидно, что кальку-
лятору предписывается выполнение этих операций с помощью ^ажатия кла-
виш на клавиатуре калькулятора, т. е. источником команд является наш па-
лец при условии сложного взаимодействия между пальцем, зрением и управ-
ляющим мозгом. И хотя калькулятор способен выполнять операции со ско-
ростями, сравнимыми с работой ЭВМ, он вынужден бесконечно ожидать каж-
дую команду, которая скажет ему, какую операцию нужно выполнить. Да-
же очень быстрый оператор калькулятора вряд ли сможет вводить больше,
чем одну команду за секунду, поэтому время, затраченное на выполнение все-
го нескольких сотен или тысяч таких операций, может быть весьма большим,
причем основную часть этого времени калькулятор работает вхолостую.
В ЭВМ неэффективность, порожденная потерянным временем между вы-
полнением одной команды и получением следующей, в течение которого про-
цессор ’’бьет баклуши”, устраняется весьма замечательным и остроумным
способом: сами команды (инструкции) хранятся в оперативной памяти ЭВМ.
Значение этой идеи состоит в том, что, когда ЦП готов обрабатывать инструк-
цию, он получает ее из своей собственной памяти. Поскольку извлечение ин-
струкции из памяти может происходить за одну миллионную секунды, то это
несомненное достижение по сравнению с калькулятором. Поэтому, если эта
схема поддерживается ”на ходу” (извлекается инструкция из оперативной
памяти, выполняется запрошенная операция, извлекается следующая ин-
струкция, если она есть, выполняется операция и т. д), всего за одну секунду
могут выполняться сотни тысяч таких инструкций. Именно эта концепция
хранимых инструкций совместно с присущей ЭВМ скоростью обработки опре-
деляет принципиальное отличие ЭВМ от карманного калькулятора.
Эти идеи приводят к некоторым интересным вопросам. Например, если
инструкция хранится в оперативной памяти, то она, очевидно, должна состо-
ять из одного или нескольких 16-битовых слов. И вспоминая, что под ин-
струкцией мы понимаем операцию (сложение, увеличение или что-либо еще)
вместе с дополнительной необходимой информацией (например, адресами
слов, над которыми операция выполняется), мы приходим к заключению,
что операция в памяти хранится как слово, а именно как строчка из 16 нулей
и единиц, поскольку такие строки являются единственными объектами, ко-
торые могут храниться в памяти. Но одно дело — дать команду калькулято-
ру с помощью нажатия кнопки +, чтобы он выполнил сложение, и совсем дру-
гое — выдать такую команду вычислительной машине, поскольку мы не мо-
жем ’’хранить” знак сложения или слово „прибавить” в оперативной памяти
ЭВМ. Информация о том, что должно производиться сложение, должна ка-
ким-то образом быть закодирована в 16-битовом слове, т. е. какая-то бито-
вая конфигурация должна однозначно определять операцию сложения. И при
выполнении операции процессор должен быть в состоянии каким-то образом
распознать эту битовую конфигурацию именно как представляющую сложе-
ние среди всех 65 5361О таких конфигураций. Иными словами, это 16-бито-
вое слово должно быть процессором декодировано. (Мы подразумеваем
57
здесь, что операционная часть инструкции занимает точно одно слово из всех
слов, составляющих инструкцию. Для ЭВМ PDP-11 это отвечает истине, хотя
в некоторых машинах, например в микро-ЭВМ, операция может занимать
отдельный байт, тогда как в других машинах для кодирования операции мо-
жет требоваться больше, чем одно слово.)
Мы уже говорили, что для достижения эффективности, присущей концеп-
ции хранимой, программы (последовательности инструкций), ЦП при выпол-
нении инструкций должен оставаться ”на ходу”, т. е. он должен знать, где рас-
положена инструкция в памяти, уметь получить ее, выполнить указанную
операцию, а также должен знать, где найти следующую инструкцию. Способ
представления такой информации в центральном процессоре весьма прост,
он подробно обсуждается в разд. 5.3.
Вопрос о доступе к инструкции приводит к другому вопросу. Из преды-
дущих глав, особенно из гл. 2 и 4, читатель, вероятно, вывел заключение, что
оперативная память является тем местом, где хранятся данные — числа, под-
лежащие обработке. Но мы теперь видим, что оперативная память является
также местом для хранения (закодированных) инструкций. Чем же тогда от-
личается 16-битовое слово, содержащее данные, от такого же слова, содержа-
щего инструкцию? Ответ — ничем. Любое 16-битовое слово в памяти может
являться фрагментом числовых данных или может представлять операцион-
ную часть какой-то инструкции. Например, предположим, что слово с адре-
сом 014426 содержит 16-битовую конфигурацию с восьмеричным представ-
лением 005267. В десятичном виде это равно 2743 и может, например, пред-
ставлять цену какого-то товара в магазине, выраженную в центах. В то же вре-
мя битовая конфигурация - это 0000101010110111, и в таком виде это
будет кодом для операции увеличения. Как в таком случае ЦП определяет,
является ли 16-битовое слово данными или инструкцией? Ответ состоит в
том, что отличить их друг от друга невозможно. Выход из этой кажущейся
хаотической ситуации состоит в том, что, когда все идет хорошо, процессору
никогда не будет указано на что-либо иное, кроме инструкции. Теперь мы
познакомимся с некоторыми из этих идей более подробно.
5.2. РЕГИСТРЫ ЦЕНТРАЛЬНОГО ПРОЦЕССОРА
В гл. 1 было сказано, что одним из аппаратных компонентов ЦП является
набор регистров. Здесь мы вкратце познакомимся с ними, а позднее в этой
главе изучим более полно. Регистр — это 16-битовое слово (или, если быть
более точным, 16-ячеечный элемент памяти), и в этом смысле он не отличает-
ся по своему поведению от 16-битовых слов в оперативной памяти. Содержи-
мое регистров может увеличиваться, уменьшаться, складываться и т. п. У них
также есть знаковый бит и возможности переноса, как у слов оперативной
памяти. Они отличаются от слов оперативной памяти в следующих отношени-
ях. Во-первых, регистры находятся не в оперативной памяти — их ’’резиден-
ция” располагается в ЦП. Во-вторых, они разрабатываются как высокоско-
ростные устройства. Поскольку большая часть действий ЦП сопряжена с эти-
ми регистрами, они собираются из таких компонентов, которые могут ме-
нять свои состояния очень быстро — намного быстрее, чем может осуще-
ствляться доступ к словам памяти и их модификация. И, наконец, регистры
более специализированы, чем слова в оперативной памяти.
58
Мы знаем, что ЦП может производить запись в слова оперативной памяти,
а также считывать содержимое этих слов. Позднее в данной главе мы позна-
комимся с некоторыми подробностями выполнения этих операций. Но у чи-
тателя должно создаться также впечатление, что мы можем тоже записывать
определенные 16-битовые конфигурации в оперативную память. Как иначе
можно было бы, например, конкретную последовательность инструкций по-
местить в оперативную память, чтобы процессор выполнял желаемые опера-
ции для получения нужного результата? И действительно, запись в оператив-
ную память может производиться извне (т. е. человеком, осуществляющим
управление) так же, как и изнутри (центральным процессором). То, как мы
можем физически записывать содержимое слов оперативной памяти, в дан-
ный момент не совсем понятно, но позднее в этой главе будут даны опреде-
ленные пояснения.
Регистры ЦП подразделяются на две категории. Регистры (16-битовые сло-
ва) первой категории — это такие, которые доступны извне в отмеченном вы-
ше смысле: они могут манипулироваться ЦП, но также могут быть прочита-
ны или записаны с какого-то внешнего источника (осуществляющим управ-
ление человеком). Ко второй категории регистров относятся такие, которые
могут манипулироваться только процессором и не могут подвергаться ка-
кому-либо вмешательству со стороны человека. Эти регистры используются
ЦП для разных целей, в основном для временного хранения данных и ад-
ресов. Эти две категории регистров мы будем называть соответственно обще-
доступными и внутренними (собственными), хотя одинаково приемлемы и
термины "доступные извне" и "недоступные извне".
Конечно, у нас нет надежды (да и нужды) осуществлять доступ к внутрен-
ним регистрам процессора. Но относительно доступа к общедоступным ре-
гистрам существует некоторая проблема. Поскольку эти регистры не нахо-
дятся в оперативной памяти, они не имеют числовых адресов и, таким обра-
зом, возникает вопрос о том, как можно на них ссылаться (адресовать) . От-
вет заключается в том, что в 16-битовой конфигурации, определяющей опера-
цию ЦП, содержится информация о том, на какие внешние регистры произ-
водится ссылка. Мы сейчас частично продвинемся в изучении этого вопроса,
но его полное решение будет понятно к концу гл. 7.
5.3. ПРОГРАММНЫЙ СЧЕТЧИК (PC) И ИЗВЛЕЧЕНИЕ ИНСТРУКЦИЙ
Теперь мы можем объяснить, как ЦП определяет, из какого места опера-
тивной памяти получить код инструкции, как осуществляет доступ к этому
слову, выполняет указанную в нем операцию и затем получает следующую
инструкцию, т. е. ’’поддерживается на ходу”. Сначала введем некоторую удоб-
ную терминологию. В любое время любой регистр содержит какое-то число,
т. е. 16-битовую конфигурацию из нулей и единиц, интерпретируемую как
двоичное представление числа. Если это число интерпретируется как адрес
ячейки памяти (слова или байта), то мы будем говорить, что регистр указы-
вает на эту ячейку памяти. Таким образом, если, например, содержимое не-
которого регистра есть 004232, то мы говорим, что регистр указывает на
ячейку памяти с адресом 004232, или (менее точно, но более компактно)
указывает на 004232. Кроме того, поскольку фраза "содержимое чего-то"
будет часто встречаться, мы предлагаем для нее такое обозначение: с(. ..).
59
Таким образом, в отмеченном примере, если мы временно обозначим наш
регистр буквой R, то можем сказать, что c(R) = 004232 и, следовательно, R
указывает на ячейку с адресом 004232. Обратите внимание, что мы ничего не
говорим о с (004232) — содержимом ячейки памяти с адресом 004232.
Один из общедоступных, или доступных извне, регистров ЦП известен
как программный счетчик (обозначаемый PC); он играет главную роль в
выполнении операций центральным процессором. Когда процессор готов вы-
полнить какую-либо операцию, он получает содержимое программного счет-
чика — число, равное с (PC), — и помещает его в некий внутренний регистр.
Затем ЦП увеличивает содержимое PC на 2. Важность этого действия вскоре
станет нам понятной. Первоначальное содержимое программного счетчика
используется как адрес. Центральный процессор получает содержимое слова
памяти, имеющего этот адрес, затем интерпретирует только что полученное
16-битовое слово как код инструкции, декодирует битовую конфигурацию
и выполняет указанную в ней операцию. Рассмотрим следующий пример.
Здесь и далее различные адреса памяти и их содержимое представлены в
восьмеричной записи.
Адрес Содержимое
РС^ 062044 005201
062046 005405
062050 -----
Мы предполагаем, что в настоящий момент времени с (PC) = 062044, т. е.
PC указывает на ячейку 062044, что показано стрелкой. Предположим, те-
перь, что ЦП готов к обработке какой-либо инструкции. Он осуществляет
доступ к содержимому PC (062044) и помещает это число во внутренний ре-
гистр, который мы будем называть регистром адреса инструкции. Затем он
увеличивает содержимое PC на 2, так что в этот момент с (PC) = 062046. Те-
перь ЦП осуществляет доступ к содержимому ячейки памяти, адрес которой
находится в (внутреннем) регистре адреса инструкции, т. е. получает содер-
жимое ячейки памяти с адресом 062044. Затем он интерпретирует получен-
ное таким образом число 005201 как операцию процессора и выполняет эту
операцию (какая это операция, для нашего обсуждения значения не име'ет,
здесь выполняется увеличение содержимого одного из общедоступных ре-
гистров ЦП). После завершения обработки этой инструкции ситуация выгля-
дит следующим образом:
Адрес Содержимое
062044 005201
РС^ 062046 005405
062050 -----
Завершив выполнение операции, помещенной в слове с адресом 062044,
ЦП готов теперь к обработке другой инструкции. Для этого он опять осуще-
ствляет доступ к содержимому PC (теперь равному 062046), сохраняя это
число в регистре адреса инструкции, увеличивая содержимое PC на 2 [так что
теперь с (PC) = 062050] и затем осуществляя доступ к содержимому ячейки
памяти, адрес которой находится в регистре адреса инструкции. Поскольку
этот регистр содержит 062046, ЦП осуществляет доступ к содержимому этой
ячейки, получая числа 005405. Он опять декодирует 16-битовое слово и вы-
полняет указанную в нем операцию (в этом случае инструкция предписывает
60
процессору очистить, т. е. установить в 0, содержимое еще одного общедо-
ступного регистра ЦП). После завершения выполнения этой инструкции си-
туация выглядит так:
Адрес Содержимое
062044 005201
062046 005405
РС-* 062050 -------
Теперь ЦП опять просматривает и увеличивает содержимое PC, получая
следующую инструкцию и т. д.
Хотя эти процессы могут показаться относительно простыми, в них вовле-
чены такие важные понятия, что на них стоит потратить некоторое время. Во-
первых, мы замечаем, что, поскольку содержимое программного счетчика
используется ЦП как адрес слова в оперативной памяти (но не байта), то со-
держимое PC должно быть четным. Центральный процессор увеличивает со-
держимое PC на 2 при каждом доступе к нему, поддерживая этим четность
с (PC). Во-вторых, мы видим, что описанный выше процесс фактически
поддерживает ЦП на ходу, а это является фундаментальным свойством ЭВМ.
Если процесс выполнения однажды запущен, то ЦП будет продолжать осуще-
ствлять доступ к последовательным словам оперативной памяти для обра-
ботки операций, т. е. выполнение будет проходить линейно по оперативной
памяти (эта концепция порождает целый ряд вопросов).
Суммируем процесс, с помощью которого ЦП получает инструкцию, сле-
дующим образом:
1. Центральный процессор получает содержимое PC и сохраняет это число
в своем временном внутреннем регистре, который мы называем регистром
адреса инструкции.
2. Центральный процессор увеличивает содержимое PC на 2 .
3. Центральный процессор осуществляет доступ к содержимому ячейки па-
мяти, адрес которой находится в регистре адреса инструкции.
(Теперь ЦП декодирует и выполняет полученную операцию.) Шаги с пер-
вого по третий будем называть извлечением инструкции, или, более просто,
извлечением. И в остальной части книги слово извлечение будет использо-
ваться только для этой цели. Хотя важны все аспекты этой концепции, мы
хотим привлечь внимание читателя к увеличению содержимого PC на 2, а так-
же к моменту этого увеличения — оно выполняется сразу же после осуще-
ствления доступа к PC. Если читателю и кажется, что мы придаем этой идее
слишком большое внимание, то вскоре в этой главе и в остальной части кни-
ги он сможет убедиться, что это значение трудно переоценить.
Приведенный выше короткий пример из нескольких последовательных
инструкций ЦП был специально продуман в следующем смысле: использо-
ванные там две операции — увеличение содержимого некоторого регистра и
очистка (сброс в 0) содержимого некоторого другого регистра — занимали
только по одному слову в оперативной памяти, чего было достаточно для их
декодирования. Рассмотрим следующий, более реальный пример:
Адрес Содержимое
РС-* 112030 005237
112032 045662
112034 ------
61
Как и прежде, мы полагаем, что ЦП готов к выполнению инструкции.
Программный счетчик содержит число (адрес) 112030. В этом случае ин-
струкция состоит из двух слов, размещенных по адресам 112030 и 112032.
Первое слово (005237) содержит код увеличения, тогда как второе слово
(045662) содержит адрес того слова, содержимое которого должно быть уве-
личено. Процессор выполняет извлечение, увеличивает содержимое PC до
112032 и получает содержимое ячейки памяти с адресом 112030, а именно
005237. В конце концов содержимое слова с адресом 045662 будет увеличе-
но, а ЦП будет готов к извлечению и выполнению следующей инструкции.
Поскольку ЦП ищет адрес следующего слова, подлежащего извлечению,
в PC, то может показаться, что с(РС) = 112032, и, таким образом,.ЦП будет
осуществлять доступ к числу 0045662 — адресу ячейки, содержимое которой
должно быть увеличено в результате выполнения последней инструкции, а не
к содержимому 112304, представляющему собой первое слово следующей
инструкции. Однако в действительности все идет нормально. К моменту, ко-
гда ЦП готов к следующей операции, содержимое PC, как и требуется, рав-
но 112034. Скрытое дополнительное увеличение с (PC) (на 2) происходит не
в результате дальнейшего извлечения, а как одно из действий самой опера-
ции увеличения (код которой есть 005237). Точное определение того, что
здесь происходит, мы отложим до гл. 7. Пока же заверяем читателя, что, ко-
гда ЦП после завершения обработки слов одной инструкции сталкивается с
многословной инструкцией, PC будет указывать на первое слово следующей
инструкции.
5.4. ЦИКЛ ВЫПОЛНЕНИЯ ИНСТРУКЦИИ
В предыдущем разделе было отмечено, что указывать на следующую опе-
рацию процессора, хранящуюся в закодированном видев оперативной памя-
ти — это функция программного счетчика. Центральный процессор выполня-
ет инструкцию путем извлечения слова и выполнения указанной в нем опера-
ции. Поскольку обработка инструкций является главным содержанием дея-
тельности вычислительной системы, имеет смысл посвятить некоторое время
несколько более детальному изучению цикла выполнения инструкции — пе-
риода, проходящего с момента завершения выполнения ЦП одной инструк-
ции до завершения следующей.
После того как ЦП выполнил последнюю инструкцию, он предпринимает
следующие действия:
1. Копирует содержимое регистра PC в свой внутренний регистр, называ-
емый регистром адреса инструкции.
2. Увеличивает содержимое регистра PC на 2.
3. Получает содержимое слова памяти, адрес которого находится в регист-
ре адреса инструкции.
С точки зрения электроники получение содержимого слова или байта из
оперативной памяти представляется не таким уж простым действием; мы по-
кажем-здесь лишь вкратце, как это происходит. Когда ЦП необходимо полу-
чить содержимое слова из оперативной памяти, он помещает адрес этого сло-
ва на адресную шину (при последующем обсуждении читателю полезно обра-
титься к рис. 1.2.1). Затем он передает сигнал, называемый сигналом чтения
по шине управления. Электронные устройства оперативной памяти отвечают
62
на сигнал чтения тем, что проверяют адрес, находящийся в настоящий мо-
мент на адресной шине. Содержимое слова, имеющего этот адрес, помещает-
ся на шину данных, и электронные устройства оперативной памяти сигнали-
зируют ЦП, передавая сигнал по шине управления, что запрошенная инфор-
мация (содержимое определенного слова) доступна на шине данных. Теперь
ЦП может прочитать состояние шины данных, чтобы получить эту информа-
цию. Как уже отмечалось, шаги 1, 2 и 3 называются извлечением инструкции.
4. Передает слово, полученное на шаге 3, в декодер — схему в устройстве
управления центрального процессора - - для определения того, какую опера-
цию предстоит выполнить. Концептуально это декодирование весьма простое.
Только что полученную битовую конфигурацию нужно просмотреть и каким-
то образом сопоставить с кодами различных допустимых операций. Как толь-
ко подлежащая выполнению операция определена, различные данные и, ве-
роятно, адреса направляются в соответствующие электронные схемы для об-
работки (вероятно, в АЛУ). Эти электронные схемы довольно сложны, по-
этому изучение сейчас их фактической работы мало что дало бы для улучше-
ния нашего понимания всей процедуры выполнения.
5. Выполняет указанную операцию.
Во время этого шага ЦП может потребоваться дополнительная информа-
ция из оперативной памяти, поскольку инструкция может состоять из слова,
содержащего саму операцию вместе с одним или несколькими дополнитель-
ными словами, определяющими, например, адреса ячеек памяти, над кото-
рыми операция выполняется.
5.5. ЗАГРУЗКА ОПЕРАТИВНОЙ ПАМЯТИ
Теперь, когда нам известно, как ЦП обрабатывает инструкции, можно от-
ветить на вопрос о том, каким образом он может различать инструкции и
данные. Когда процессор готов к выполнению инструкции, эта инструкция
может быть выполнена, если PC содержит адрес 16-битового слова, представ-
ляющего битовую конфигурацию некоторой инструкции. Если же получается
так, что PC содержит адрес того, что мы предполагаем использовать в каче-
стве некоторого фрагмента данных, то эти данные (как 16-битовое слово)
будут извлечены ЦП, если возможно, декодированы и выполнены. Вероятно,
такую ситуацию — выполнение слов, предназначенных для использования в
качестве данных — читатель сочтет губительной (каковой
она и является), но ему также следует понять, что ее лег-
ко можно избежать. Как только ЦП завершает выполне-
ние некоторой инструкции, он извлекает следующее по-
следовательное слово оперативной памяти для выполне-
ния, испрльзуя для этого PC. Таким образом, нам лишь
необходимо гарантировать, чтобы инструкции, которые
мы хотим выполнять, располагались в оперативной памя-
ти в последовательных ячейках, а любые данные с кото-
рыми оперируют инструкции, располагались вне основ-
ного потока этих последовательно загружаемых инструк-
ций. Поэтому, когда должна выполняться некоторая по-
следовательность инструкций, оперативная память обыч-
но выглядит так, как показано на рис. 5.5.1.
Область данных
Последовательные
инструкции в
последовательных
ячейках памяти
Область данных
Рис. 5.5.1
63
Рассмотрим следующий пример. Предположим, что мы хотим сложить два
числа и, сделав это, хотим уменьшить результирующую сумму на 1. Операция
сложения в ЭВМ PDP-11 выполняется так, что сумма содержимого двух яче-
ек памяти помещается во вторую из этих ячеек. (То, что мы намереваемся
сделать, на языке высокого уровня, таком как Бейсик или Фортран, может
быть записано как У = X + У — 1.) Пусть память должна быть загружена сле-
дующим образом:
Адрес Содержимое Комментарии
РС> 047604 063737 Код для операции сложения -
047606 100424 прибавить с(100424) кс(1О2552) и оставить
047610 102552 результат в ячейке 102552
047612 005337 Код для операции уменьшения -
047614 102552 уменьшить с (102552).
047616 000000 Код для операции ’’остановить процессор”.
047620 — — —
100424 003413 Первое слагаемое.
102552 022212 Второе слагаемое.
Если мы гарантируем, что PC содержит число (адрес) 047604, то ЦП будет
выполнять извлечение, осуществляя доступ к содержимому PC и увеличивая
это содержимое на 2, так что с (PC) = 047606, и затем получая содержимое
ячейки с адресом 047604, а именно 063737. Теперь ЦП декодирует эту бито-
вую конфигурацию и узнает, что предстоит выполнить операцию сложения.
Информация в этих 16 кодовых битах говорит также центральному процес-
сору, что он найдет адреса чисел, подлежащих сложению, в следующих двух
последовательных словах оперативной памяти, которыми являются 047606 и
047610. Центральный процессор получает и складывает содержимое ячеек с
адресами 100424 и 102552 и помещает результирующую сумму назад в ячей-
ку с адресом 102552. Таким образом, к моменту завершения инструкции
сложения мы имеем с(100424) = 003413, с(102552) = 025625 (003413 +
+ 022212) ис(РС) =047612. [Вспомним, что с (PC) было увеличено до 047606
в результате извлечения процессором содержимого ячейки 047604. Содержи-
мое программного счетчика увеличилось еще на 4, до 047612, не в результате
последующего извлечения центральным процессором, а из-за самой инструк-
ции сложения. Напоминаем опять, что разъяснения по этому поводу будут да-
ны в гл. 7. Пока же замечаем, что PC правильно указывает на следующую
инструкцию, подлежащую выполнению.] Поскольку с (PC) = 047612, то со-
держимое этого слова извлекается и декодируется как инструкция ’’Умень-
шить содержимое слова памяти, адрес которого находится в следующем сло-
ве”. Центральный процессор получает содержимое следующего слова (102552)
и уменьшает содержимое слова, имеющего этот адрес. Таким образом, в мо-
мент завершения операции уменьшения с (10255 2) = 025624ис(РС) =047616.
Опять не совсем ясно, как содержимое программного счетчика было увели-
чено еще на 2, но это произошло из-за самой инструкции уменьшения.
64
Завершив инструкцию уменьшения, ЦП производит другое извлечение:
увеличивает с (PC) на 2 до 047620, получает содержимое ячейки с адресом
047616, а именно 000000, и декодирует это слово. А это есть битовая конфи-
гурация для операции ’’остановить процессор”, поэтому ЦП переходит в со-
стояние останова, прекращая последовательность извлечений и выполнений.
Заметим,-что здесь были выполнены три инструкции: сложить, уменьшить
и остановить. Обратите также внимание, что слова, извлеченные ЦП, были по-
лучены из последовательных ячеек памяти. И наконец, надо отметить, что
мы внимательно следили за тем, чтобы слова, содержащие данные, которые
обрабатывались этой последовательностью инструкций, располагались за пре-
делами самих этих инструкций.
Можно заявить о своем желании размещать некоторые 16-битовые слова в
определенных ячейках памяти, но как мы можем гарантировать, что зти сло-
ва действительно будут там размещены! То есть, как различные слова опера-
тивной памяти могут быть загружены конкретным содержимым? На некото-
рых машинах, включая ряд модели ЭВМ PDP-11, имеется так называемая ’’пе-
редняя панель”, содержащая переключатели, которые можно использовать
следующим образом. На 16 таких переключателях (каждый представляет 0
или 1) мы можем установить определенный адрес, представленный двоич-
ным числом. Выбрав этот адрес, мы можем теперь переустановить на пере-
ключателях 16-битовое двоичное число и, нажимая другой переключатель, за-
нести 16-битовое слово, представленное текущим состоянием 16 переключа-
телей, в предварительно выбранный адрес. Таким путем мы можем поме-
стить (занести) любое конкретное 16-битовое слово в любую ячейку памяти.
Использовав переключатели для установки содержимого оперативной памя-
ти по своему желанию, мы с их же помощью можем загрузить определенное
число в программный счетчик. И сделав так, можем нажать переключатель
START, чтобы процессор начал выполнение с адреса, помещенного в PC.
Таким путем мы можем загрузить восемь слов, необходимых для преды-
дущего примера (шесть слов инструкций от ячейки 047604 до ячейки 047616
включительно и два слова данных в ячейках 100424 и 102552), установить
в PC значение 047604 и затем запустить ЦП. За несколько миллионных долей
секунды ЦП достигнет состояния останова с PC, содержащим 047620, и мы
сможем, если хотим, использовать переключатели для просмотра содержимо-
го слова с адресом 102552 — ’’ответа”. На передней панели некоторых послед-
них моделей ЭВМ PDP-11, так называемых ’’битовых переключателей” нет, а
указание адресов памяти и загрузка этих слов конкретным содержимым
происходит с помощью консольного терминала — терминала, подключенного
в некотором смысле непосредственно к ЦП. Этот терминал может использо-
ваться также для просмотра слов памяти, загрузки PC и запуска выполнения
инструкций процессором.
Одно дело загрузить несколько слов, необходимых для выполнения ин-
струкций предыдущего примера, и совсем другое — загрузить длинную (ве-
роятно, несколько тысяч слов) последовательность инструкций. Очевидно,
что загружать память с помощью этих переключателей в таком случае просто
непрактично, да фактически эти переключатели никогда в действительности
и не предназначаются для подобной цели. Существуют намного более эффек-
тивные средства для размещения слов в оперативной памяти, и в дальнейшем
3 Зак. 2212 гс
мы познакомимся с некоторыми из них. Пока же заверяем читателя, что су-
ществует возможность установки определенных значений в конкретных
ячейках памяти.
5.6. СЛОВО СОСТОЯНИЯ ПРОЦЕССОРА (PSW)
Обсуждая арифметику над четырехбитовыми словами в гл. 2, мы замеча-
ли, что при некоторых условиях происходят переносы в самый старший бит
или из него, сигнализирующие, в свою очередь, о возможной неправильности
арифметических результатов. Конечно, аналогичные проблемы могут возни-
кать и в случае 16-битовых слов. В то время мы настаивали на том, что при
некоторых обстоятельствах ЦП будет устанавливать индикаторы переноса и
переполнения, которые могут быть затем проверены. И если есть подозрение,
что какая-то арифметическая операция дает искаженный результат, то могут
быть предприняты соответствующие корректирующие действия. Однако мы
не пояснили, что собой представляют эти индикаторы и как они могут быть
проверены. В этом разделе мы рассмотрим, что это за индикаторы, а в следу-
ющем — как с ними можно обращаться.
Мы уже обсудили один из общедоступных регистров ЦП — программный
счетчик PC. В ЦП имеется другой такой доступный извне регистр — слово со-
стояния процессора, для которого используется сокращение PSW, или иногда
просто PS. Как следует из названия, содержимое этого регистра сообщает
нам существенную информацию о текущем состоянии процессора. Частичный
вид этого слова показан на рис. 5.6.1.
(Битовая
позиция)
15 3210
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 N Z V с
Рис. 5.6.1
Четыре бита PSW, показанные на рис. 5.6.1, называются так:
бит 3 — N — индикатор отрицательного результата, или бит отрицательного
результата;
бит 2 - Z — индикатор нуля, или бит нуля;
бит 1 — V — индикатор переполнения, или бит переполнения;
бит 0 - С — индикатор переноса, или бит переноса.
(Естественно было бы обозначить индикатор переполнения буквой О, но мы
использовали букву V, чтобы не создавать путаницу между буквой О и циф-
рой О1).) Большинству оставшихся битов этого слова (биты от 15 до 4) так-
же придается определенное значение, но мы отложим знакомство с ними до
тех пор, пока не будет изложен материал гл. 12.
Биты PSW от 3 до 0 называются кодами условий ЦП. Как отмечалось в
гл. 2, установка и сброс этих битов зависят от конкретной выполняемой опе-
рации, но мы сделаем по поводу этих битов некоторые общие, хотя ине впол-
не точные выводы. Когда ЦП завершает обработку операции арифметическо-
го типа, он соответствующим образом устанавливает или сбрасывает эти би-
ты. В частности, ЦП обычно устанавливает бит С при наличии переноса из бита
Английский оригинал слова ’’переполнение” начинается с буквы О; первой соглас-
ной буквой в этом слове является V. - Прим, перев.
66
15 и устанавливает бит V при наличии переноса из бита 14 в бит 15. Мы вновь
предупреждаем читателя, что установка и сброс этих двух битов зависят от
операции, а точные детали можно узнать путем внимательного изучения опи-
сания операции (см. Приложение А). В то же время поведение битов Z и N
более предсказуемо. Если результатом арифметической операции является
нуль, то ЦП устанавливает бит Z в PSW. Если нет, то ЦП сбрасывает бит Z.
И, наконец, если число результата отрицательное, то ЦП устанавливает бит
N (в 1), в противном случае он сбрасывает его (в 0). Таким образом (как
это видно из следующего примера), по завершении каждой операции мы рас-
полагаем некоторыми средствами для определения того, что случилось в ре-
зультате этой операции.
Рассмотрим сложение двух чисел 064223 и 043002. Результат равен 127225.
Как мы видим, результирующее число отрицательное и не нулевое, имеет ме-
сто перенос из бита 14 в бит 15,но перенос из бита 15 отсутствует. Таким об-
разом, после завершения этой операции коды условий в PSW будут иметь
следующие значения:
N= 1 V=1
z=o с = о
Поэтому, если мы рассматриваем эти два числа как целые без знака, то мо-
жем заключить на основании того, что С = 0 (т. е. нет переноса из бита 15),
что результат арифметически правильный. Если мы рассматриваем числа как
имеющие знак, то бит переполнения показывает, что произошло изменение
знака (перенос в знаковый бит) и, следовательно, арифметический резуль-
тат неправильный.
Слово состояния процессора является уникальным среди регистров ЦП
в том смысле, что этот регистр обладает адресом, а именно 777776, и все же
не является словом оперативной памяти. Таким образом, предположительно
мы можем осуществлять доступ к PSW по адресу точно так же, как это дела-
ется со словом оперативной памяти. И хотя действительно возможно, мы
редко так поступаем. В настоящий момент наш интерес к PSW заключается
в определении кодов условий, однако существуют более легкие пути провер-
ки этих битов, чем действительно ’’просмотр” PSW.
5.7. ИНСТРУКЦИЯ УСЛОВНОГО ПЕРЕХОДА
Обсуждая роль, которую играет программный счетчик в выполнении ин-
струкций, мы (вполне корректно) заключили, что выполнение инструкций
происходит линейно — одно последовательное слово за другим извлекается
из оперативной памяти и выполняется ЦП, поэтому коды инструкций долж-
ны загружаться в последовательно адресуемые слова памяти. Из своего прош-
лого опыта программирования читатель, несомненно, знает: нередко логика
программной процедуры диктует, что при некоторых обстоятельствах такое
последовательное выполнение должно останавливаться, а управление как-то
передаваться к другому сегменту инструкций. Действительно, без таких воз-
можностей передачи управления применимость концепции хранимых ин-
струкций, столь много обещающая, была бы сильно ограничена. Например,
невозможными были бы хорошо известные циклы в программах. Поэтому
нам необходимы какие-то средства, чтобы можно было оставить обработку
3*
67
002442
002444
PC —►
Выполнение инструкций начинается здесь.
--- 005314 Прекратить линейную обработку, перейти
к 023660.
>. 023660 Продолжить обработку здесь.
Рис. 5.7.1
инструкций в одной части оперативной памяти и возобновить обработку где-
то в другом месте памяти. И мы действительно располагаем простым спосо-
бом осуществления этого.
Рассмотрим следующий пример, в котором предполагается, что обработ-
ка началась с ячейки 002442 и продолжается непрерывно до ячейки 005314,
причем в этой точке мы хотим прервать линейную (по последовательным
словам) обработку и начать выполнение с ячейки 023660. В действительно-
сти, если иметь в виду размещение операций, подлежащих выполнению, то
мы хотим перескочить из ячейки 005314 в ячейку 023660 (рис. 5.7.1). Нам
необходимо только поместить в слово с адресом 005314 код инструкции,
предписывающей ’’Поместить число 023660 в PC” (такая инструкция суще-
ствует). Давайте посмотрим, что происходит, когда встречается такая ин-
струкция.
Когда ЦП готов начать выполнение, он осуществляет доступ к содержимо-
му PC (а именно, 002442), увеличивает с (PC) на 2 до 002444, извлекает со-
держимое ячейки 002442, декодирует это 16-битовое слово и выполняет опре-
деленную в нем инструкцию. Затем ЦП повторяет этот цикл выполнения.
В конце концов ЦП осуществляет доступ к PC и находит, что его содержимое
равно 005314. Центральный процессор увеличивает с (PC) до 005316 и извле-
кает и декодирует содержимое ячейки 005314. Вспомним о нашем предполо-
жении, что инструкция, помещенная в ячейке 005314, является такой опера-
цией, которая размещает число 023660 в PC. Таким образом, к моменту за-
вершения выполнения этой инструкции мы имеем с (PC) = 023660. Теперь
ЦП готов к выполнению следующей инструкции и, как обычно, осуществля-
ет доступ к PC для определения адреса ячейки памяти, содержащей следую-
щую инструкцию. Осуществляя доступ к PC теперь, он получает адрес 023660,
увеличивает PC до 023662, извлекает содержимое ячейки 23660, декодиру-
ет и выполняет его. Таким образом, нам удалось перевести выполнение ин-
струкций процессором из одного места оперативной памяти (005314) в дру-
гое (023660), просто подстроив содержимое программного счетчика. Эта
идея совсем проста, но порождает весьма разнообразные последствия.
Читателю, обладающему некоторым опытом программирования на языке
высокого уровня (например, Бейсике или Фортране), известно, что такой пе-
ревод из одной последовательности инструкций в другую не является чем-то
необычным в подобных языках; это так называемый безусловный переход,
обычно представляемый термином GOTO. Но читатель также знает, что более
68
полезным типом оператора передачи управления является условный переход,
имеющий обычно такую форму:
IF (условие) THEN GOTO (куда-то)
Этот оператор выполняется следующим образом. Если заданное условие ис-
тинно, то управление передается в указанное место для продолжения обра-
ботки. Но если условие ложно, то этот оператор просто игнорируется, а вы-
полнение продолжается линейно со следующего оператора. В ЭВМ PDP-11
реализован целый набор операций, соответствующих этим условным перехо-
дам; они называются условными ветвлениями. Одну из них мы детально изу-
чим в этом разделе. За исключением условия, по которому инструкция по-
рождает или не порождает ветвление (или переход), все условные ветвления
ведут себя точно так же, как та инструкция, с которой мы здесь познако-
мимся.
Неформально нашу инструкцию условного ветвления мы будем называть
ветвление-если-не-ноль, а ее действие опишем следующим образом. Если бит
Z (бит 2) в слове состояния процесса равен 0 в момент декодирования этой
инструкции, то инструкция порождает ветвление (куда-то). Если же в мо-
мент декодирования инструкции бит Z равен 1, то ЦП игнорирует эту ин-
струкцию и продолжает обработку со следующей инструкции, т. е. со следу-
ющего слова оперативной памяти. Таким образом, используя инструкцию
ветвление-если-не-ноль, мы в действительности проверяем один из кодов
условий в слове состояния процессора — бит Z,ho такая проверка выполня-
ется не столь прямолинейно, как ’’просмотр” этого бита (что бы под этим ни
подразумевалось). Вместо этого мы инструктируем процессор посмотреть
на этот бит и затем выполнить одно действие или другое в зависимости от
того, установлен он или сброшен.
Поскольку ясно, что ветвление (или переход) может достигаться путем
изменения содержимого PC, читатель может предположить, что ветвление-ес-
ли-не-ноль есть двухсловная инструкция, когда первое слово содержит код
самого условного ветвления, а второе слово — это число (адрес), которое
должно быть помещено в PC для порождения ветвления (если бит Z равен
0). И хотя эта догадка совершенно правомерна, в ЭВМ PDP-11 инструкция
ветвление-если-не-ноль, которую более правильно было бы назвать ветвле-
ние-если-бит-Х-сброшен, является однословной, но формируемой несколько
сложнее обычного. Однако во время нашего обсуждения следует помнить о
том, что если ветвление (или переход) имеет место, то оно должно достигать-
ся путем изменения содержимого программного счетчика.
Формат битовой конфигурации, которая представляет (будет декодирова-
на как) ветвление-если-не-ноль, таков:
00 000 010 хх ххх ххх
Старший байт (00 000 010) — зто код, распознаваемый декодером как вет-
вление-если-не-ноль. Младший байт (хх ххх ххх) требует некоторых поясне-
ний. Содержимое младшего байта представляет смещение; использование
этого термина вскоре станет понятным.
Для выполнения ветвления ЦП должен изменить содержимое PC. Цент-
ральный процессор может сделать это либо путем загрузки некоторого ново-
го числа непосредственно в PC, либо путем прибавления положительного или
69
отрицательного числа к содержимому PC, придавая ему тем самым некото-
рое новое значение. Такое число, прибавляемое к с(РС), называется смеще-
нием программного счетчика. Поэтому, если текущее содержимое PC равно,
например, 020042 и ЦП выполняет инструкцию, которая прибавляет число
000150 к с (PC), то после выполнения этой инструкции с (PC) = 020212. Тем
самым осуществлено ветвление (или переход) из ячейки 020042 в 020212.
Центральный процессор использует некоторым образом младший байт ин-
струкции условного ветвления-если-не-ноль для смещения (подстройки) те-
кущего содержимого программного счетчика, если бит Z в слове состояния
процессора равен 0 во время выполнения этой инструкции.
Читатель может предположить, что младший байт инструкции задает чис-
ло, которое фактически прибавляется к с (PC), но это не так. Чтобы это по-
нять, рассмотрим числа, представленные здесь в десятичном виде, занимаю-
щие восьмибитовый байт; их диапазон простирается от 0 до 255, если рас-
сматривать их без учета знака. Но когда мы имеем дело только с числами без
знака, любое смещение PC всегда будет происходить в направлении верхних
адресов памяти, и не останется никакой надежды на ветвление назад к нижним
адресам памяти, которое обычно имеет место при построении цикла в после-
довательности инструкций. Поэтому нам придется рассматривать младший
восьмибитовый байт как число со знаком. Диапазон таких чисел простирает-
ся от —128 до +127, причем бит 7 действует как знаковый бит.
Вспомним теперь, что программный счетчик всегда указывает на слово
(содержащее инструкцию) и, таким образом, с (PC) всегда четное. Посколь-
ку с (PC) должно оставаться четным и после смещения, то можно заключить,
что если младший байт инструкции ветвление-если-не-ноль дает само смеще-
ние, то он также должен быть четным. Поэтому фактически возможными
(десятичными) числами, которые могут использоваться в младшем байте,
будут —128, —126, . .. , 0, 2,4,... , 124, 126. Мы заключаем, что эта инструк-
ция позволяет осуществлять ветвление назад на 128 байт И вперед на 126
байт, или, что эквивалентно, назад на 64 слова и вперед на 63 слова. Заметим,
что бит 0 всегда должен быть нулевым, поскольку, как мы знаем, смещение
должно быть четным; таким образом, бит 0 в младшем байте ’’теряется”.
Для устранения потерь, связанных с битом 0, иодновременного расшире-
ния (фактически удвоения) диапазона, на который может подстраиваться со-
держимое PC для ветвлений, число в младшем байте мы считаем пословным
смещением, а не байтовым смещением PC. Тогда процессору для получения
сдвига нужно только удвоить это пословное смещение. Таким образом, со-
держимое PC может быть модифицировано в диапазоне от —128 до+ 127 слов,
или, что эквивалентно, от —256 до +254 байт. Формальное описание ветвле-
ния-если-не-ноль выглядит так:
Ветвление-если-не-ноль:
если бит Z в PSW во время декодирования инструкции равен 1, то ин-
струкция игнорируется и обработка продолжается со следующего слова
оперативной памяти;
если бит Z в PSW во время декодирования инструкции равен 0, то млад-
ший байт инструкции, взятый как число со знаком, удваивается, и полу-
ченное в результате удвоения число (со знаком) прибавляется к содержи-
мому PC.
70
Символически: PC *- с (PC) + 2 х (младший байт) =
= с (PC) + 2 х (пословное смещение со знаком)
Нотация *- означает ’’присваивается значение”.
Действие этой инструкции продемонстрируем на нескольких простых при-
мерах.
Предположим, что, когда ЦП готов выполнить инструкцию, с (PC) = 023042
и с (023042) =001031.
Адрес Содержимое
PC-* 023042 001031
023044 ----
Центральный процессор выполняет извлечение, увеличивая с (PC) на 2; по-
этому теперь с(РС) = 023044. Извлеченное слово, 001031, пересылается в де-
кодер, который интерпретирует его как ветвление-если-не-ноль. Если бит Z
в PSW равен 1, то ЦП просто начинает следующий цикл выполнения, извле-
кая содержимое ячейки 023044 и т. д. Если бит Z равен 0, то происходит
ветвление, и мы вычисляем смещение этого ветвления. В двоичном виде ин-
струкция ветвление-если-не-ноль выглядит так:
00 000 010 00 011 001
Младший байт (пословное смещение со знаком) имеет значение 031 (восьме-
ричное), которое удваивается для получения смещения PC (062) и затем
прибавляется к с (PC). Однако нам должно быть известно, что к этому мо-
менту с (PC) = 023044. Выполняя сложение, получаем 023044 + 062 = 023126,
что становится новым содержимым программного счетчика. Завершив вы-
полнение этой инструкции, ЦП, как обычно, ищет в PC адрес следующей ин-
струкции и находит число (адрес) 023126. Таким образом, инструкция вет-
вление-если-не-ноль вызывает переход в ячейку 023126 из ячейки 023042
(при условии, конечно, что бит Z равен 0) .
В качестве другого примера рассмотрим следующую ситуацию:
Адрес Содержимое
РС-* 023422 001315
023044 ----
Опять мы полагаем, что с (PC) = 023042, ЦП выполняет извлечение, увеличи-
вая с (PC) до 023044, и инструкция декодируется как ветвление-если-не-ноль.
Восьмеричному числу 001315 соответствует следующая битовая конфи-
гурация:
00 000 010 11001 101
и, таким образом, если ветвление должно произойти (если бит Z равен 0), то
пословное смещение в младшем байте равно 315 (в восьмеричном виде).
Как восьмибитовый байт это число отрицательное - оно равно отрицательно-
му значению (восьмибитового восьмеричного) числа 063. Удвоение этого чи-
сла дает 146, что является байтовым смещением, которое должно быть выч-
тено из с (PC). Таким образом, новое значение содержимого PC есть 023044 -
— 146 = 022676, и мы получили переход назад, к нижнему адресу оператив-
ной памяти.
Если читатель обеспокоен тем фактом, что мы истолковываем положи-
тельные и отрицательные пословные смещения по-разному (прибавляя бай-
товое смещение в первом случае и вычитая отрицательное значение байтово-
71
го смещения во втором), то ему предлагается некоторая универсальная схе-
ма, не зависящая от знака смещения и более полно отражающая фактичес-
кую процедуру, которой следует процессор. Рассмотрим некоторое слово
(внутренний регистр), значение младшего байта которого совпадает с млад-
шим байтом инструкции условное ветвление-если-не-ноль. Как мы знаем,
этот байт является пословным смещением. Мы сдвигаем этот байт влево на
один бит следующим образом. Бит 7 сдвигаем из байта вовне, но сохраняем
его значение, 0 или 1. Сдвигаем бит 6 в бит 7, бит 5 в бит 6,. . . , бит 0 в бит 1
и заполняем покинутый бит 0 значением 0. Теперь мы заполняем старший
байт слова тем битовым значением, которое было сдвинуто из бита 7. Этот
процесс называется распространением знака на старший байт слова, посколь-
ку старший байт оказался заполненным значением первоначального знаково-
го бита (бита 7) младшего байта. В результате этой процедуры порождается
слово, значение которого с учетом знака является удвоенным значением со
знаком младшего байта инструкции условного ветвления. (Объясните, по-
чему.) Таким образом, младший байт удвоен, как требуется, а знаковый бит
распространен на старший байт. Это слово теперь просто прибавляется к те-
кущему содержимому PC для получения ветвления, и мы замечаем, что ни-
какого решения о положительности или отрицательности пословного смеще-
ния не принимается.
Рассмотрим вновь два приведенных выше примера. В первом младший
байт инструкции равен 00 011 001. Когда мы сдвигаем его влево на 1 бит,
то получаем в результате 00 НО 010 и,поскольку из бита 7 был выдвинут
0, старший байт внутреннего регистра заполняется нулями, что дает слово
0 000 000 000 110 010 = 000062. Когда мы прибавляем это слово к с (PC) =
= 023044, результат получается таким же, как прежде: 023044 + 000062 =
= 023126. Во втором примере младший байт инструкции ветвления равен
И 001 101. Сдвиг его влево дает в результате 10 011 010, и поскольку из
бита 7 была выдвинута 1, слово с распространенным знаком, представляю-
щее байтовое смещение, есть 1 111 111 110 011 010= 177632. Прибавляя это
слово к с (PC), получаем, как и прежде, 023044 + 177632 = 022676.
Есть много других инструкций условного ветвления, с которыми мы
встретимся в примерах в оставшейся части книги. Они проверяют другие ко-
ды условий в PSW или, в некоторых случаях, комбинации этих кодов. Одна
же из этих инструкций не делает вообще никакой проверки — она выполняет
безусловное ветвление. Безусловные ветвления, как мы увидим, образуют
один из наиболее часто используемых классов инструкций, и читатель при
разработке последовательностей инструкций для выполнения различных за-
дач найдет их просто необходимыми. Как уже упоминалось, хотя старшие
байты этих условных ветвлений различаются (биты этих байтов определяют,
какой тип проверки должен выполняться над кодами условий) , во всех слу-
чаях младший байт всегда берется как пословное смещение со знаком. Поэто-
му поведение конкретной инструкции ветвление-есть-не-ноль действительно
типично для всех инструкций этого класса.
5.8. ДВЕ ПОЛНЫХ (ХОТЯ И НЕИНТЕРЕСНЫХ) ПРОГРАММЫ
До сих пор мы специально,насколько это возможно,избегали использова-
ния термина программа, но сейчас дадим неформальное определение. Под
72
программой мы будем понимать последовательность инструкций ЦП вместе
с данными, которыми эти инструкции оперируют; при выполнении эти ин-
струкции образуют некоторую содержательную функцию. Можно также на-
стаивать на том, что после выполнения последовательности инструкций ЦП
в конце концов останавливается. Мы понимаем, что такое определение пред-
ставляется весьма неясным, особенно, что касается слова содержательное, но
формальное определение, если кто-либо вообще на нем настаивает, настолько
абстрактно, что для наших целей оказалось бы бесполезным. В смысле не-
формального определения последовательность инструкций из разд. 5.5, кото-
рая складывает два числа, уменьшает результат и затем останавливается, яв-
ляется программой. В данном разделе мы представляем две такие програм-
мы, хотя их содержательность остается, конечно, под вопросом. Принципи-
альное их назначение состоит в том, чтобы ввести понятие итерации выпол-
нения инструкций — цикла инструкций, которые выполняются снова и сно-
ва. В конце концов итерация завершается в результате выполнения инструк-
ции ветвление-если-не-ноль, проверяющей бит Z в PSW.
Первая программа раз за разом увеличивает содержимое слова памяти, ко-
торое имеет начальное значение —3, до тех пор, пока содержимое этого слова
не станет равным нулю. Тогда программа предписывает процессору остано-
виться. Адреса, в которые загружены инструкции, выбраны произвольно.
Адрес Содержимое Комментарии
076606 177775
Число, подлежащее увеличению, -3.
104302 005237 Увеличить содержимое ячейки
104304 076606 с адресом 076606.
104306 001375 Ветвление-если-не-ноль.
104310 000000 Остановить ЦП.
104312 —
Мы полагаем, что первоначально с (PC) = 104302, так что первой инструк-
цией, которая должна быть выполнена, является двухсловная инструкция
для увеличения содержимого слова памяти с адресом 076606. Когда это сде-
лано, данное слово содержит 177776. В результате этого увеличения коды
условий устанавливаются следующим образом:
№1
Z=0
v = o
(На бит С эта операция никакого действия не оказывает, но нас он и не инте-
ресует.) Бит N устанавливается, поскольку результирующее число 177776
(= —2) — отрицательное. Бит Z сбрасывается, поскольку результирующее
число не равно 0, и именно этот бит представляет для нас интерес. (Бит V
73
также сбрасывается, поскольку перенос в бит 15 не происходит.) В этот мо-
мент с (PC) = 104306, т. е. PC указывает на инструкцию ветвление-если-не-
ноль, которая извлекается, а содержимое PC увеличивается до 104310. По-
скольку бит Z равен 0, то ветвление будет происходить, и мы должны вычис-
лить для него смещение. Младший байт инструкции равен 375 (в восьмерич-
ном виде), т. е. представляет собой отрицательное значение числа 003. По-
скольку это пословное смещение, мы удваиваем его для получения байтово-
го смещения —006. Прибавляем это число к содержимому PC и находим, что
новое значение программного счетчика будет равно 104310 — 006 = 104302.
Таким образом, управление возвращается назад, к инструкции увеличения.
Содержимое ячейки 076606 вновь увеличивается (что дает 177777), и,
поскольку результат опять не нулевой, бит Z сбрасывается, а когда во вто-
рой раз встречается инструкция ветвление-если-не-ноль, она опять порожда-
ет ветвление назад, к инструкции увеличения. На этот раз увеличиваемое
число есть 177777, и результат увеличения будет равен 000000. Поскольку
это 0, ЦП установит бит Z (в 1). Опять с (PC) = 104306, так что ЦП извлекает
содержимое ячейки 104306 (инструкцию ветвление-если-не-ноль), в резуль-
тате чего, как и прежде, с (PC) увеличивается до 104310. Поскольку провер-
ка бита Z выявляет, что он установлен, ЦП игнорирует инструкцию ветвле-
ния и начинает цикл выполнения другой инструкции. Поскольку с (PC) =
= 104310, извлекается инструкция останова, код которой есть 000000, а со-
держимое PC увеличивается до 104312. Эта инструкция выполняется и ЦП
останавливается. Таким образом, после останова мы имеем с (PC) = 104312
и с (076606) = 000000. Содержимое других ячеек памяти не изменяется.
Важность этого примера заключается в том, что некоторая инструкция (в
нашем случае инструкция ’’Увеличить содержимое слова с адресом 076606”)
выполнялась повторно, и выполнение было остановлено при достижении не-
которого условия, а именно, когда содержимое этого слова стало нулевым.
Наш следующий пример немного содержательнее и несколько сложнее, а
также обладает довольно необычной особенностью: программа фактически
модифицирует одну из своих собственных инструкций в ходе ее выполнения.
Назначение программы состоит в сложении пяти чисел, которое осуществля-
ется следующим образом. Одна ячейка памяти содержит число 5 и работает
как счетчик для управления числом итераций некоторой последовательности
инструкций. Это число будет уменьшаться на 1 каждый раз при прохождении
цикла, а выход из цикла произойдет, когда число станет равным 0; послед-
нее условие, как и прежде, проверяется инструкцией ветвление-если-не-ноль.
Пять слагаемых чисел размещаются в пяти последовательных ячейках па-
мяти, и мы убедимся в значении того факта, что эти ячейки последователь-
ные, или смежные. Нам также необходимо знать, что инструкция сложения
ЭВМ PDP-11 вычисляет сумму двух чисел и помещает результат назад на ме-
сто второго из этих чисел. Таким образом мы инициализируем слово памяти
в 0, а сумма чисел будет в этой ячейке накапливаться. Программа показана
на рис. 5.8.1.
Мы полагаем, что выполнение начинается с ячейки 047032, т. е. первона-
чально с(РС) = 047032. Слово из этой ячейки извлекается и декодируется, в
результате чего становится известно, что нужно ’’Увеличить содержимое сло-
ва, адрес которого находится в следующем слове (т. е. адрес которого нахо-
74
Адрес
Содержимое Комментарии
047032 005237 Увеличить содержимое
047034 047044 слова в 047044.
047036 005237 Увеличить содержимое
047040 047044 слова в 047044.
047042 063737 Прибавить содержимое слова в ??????
047044 102602 к содержимому слова в 104004,
047046 104004 оставляя сумму в ячейке 104004
047050 005337 Уменьшить содержимое
047052 074010 слова в 074010.
047054 001366 Если не ноль, ветвление в 047032.
047056 000000 Остановить ЦП.
074010 000005
Счетчик (первоначально равен 5).
102604 003013 Это
102606 102000 есть
102610 000003 числа,
102612 012340 подлежащие
102614 100114 сложению.
104004 000000
Сумма накапливается здесь.
Рис. 5.8.1
дится в ячейке 047034)”. Таким образом, увеличиваться должно слово с ад-
ресом 047044, а именно слово 102602. Это слово увеличивается до 102603 и
извлекается и декодируется следующая инструкция. Как видим, она идентич-
на первой обработанной инструкции, и слово в ячейке 047044 увеличивается
вновь. Таким образом, теперь слово в ячейке 047044 содержит 102604, а
с(РС) = 047042. Код следующей инструкции (063737) извлекается из ячейки
047042 и декодируется как ’’Сложить содержимое двух слов, адреса которых
находятся в следующих двух ячейках памяти, а сумму поместить во второе
из этих слов”. Дальнейшее требует определенного внимания. В листинге со-
держимого оперативной памяти,приведенном на рисунке, слово, следующее
за инструкцией сложения, т. е. слово в ячейке 047044, показано равным
102602. Но вспомним, что это слово уже дважды было увеличено (на 1). По-
этому в этой точке содержимое ячейки 047044 есть 102604 и представляет
собой адрес первого из двух слов, подлежащих сложению. Адрес второго
слагаемого слова следует непосредственно за ним, а именно 104004 (в ячей-
ке 047046). Таким образом, с(102604) прибавляется к с(104004), что дает
003013 + 000000 =003013 в качестве результата, который помещается назад,
в ячейку 104004. Теперь с (PC) = 047050.
Извлекается и выполняется следующая инструкция (’’Уменьшить содер-
жимое слова с адресом 074010”), в результате чего число в ячейке 074010
75
становится равным 4. Поскольку этот результат не нулевой, бит Z в PSW
сбрасывается (устанавливается в 0). В этот момент с (PC) = 047054, извлека-
ется инструкция ветвление-если-не-ноль, а с (PC) увеличивается до 047056.
Поскольку бит Z сброшен, происходит ветвление, и нам необходимо вычис-
лить его смещение. Младший байт инструкции ветвления (в восьмеричном
виде) равен 366, что как восьмибитовое число со знаком равно —012. Удвое-
ние его дает байтовое смещение, равное —024, которое прибавляется к с (PC).
Таким образом, к моменту завершения выполнения этой инструкции мы
имеем с (PC) = 047056 — 024 = 047032. Следовательно, выполнение возобнов-
ляется здесь (по адресу 047032), и мы возвращаемся назад, к первой из двух
инструкций увеличения. Содержимое слова в ячейке 074044 опять увеличива-
ется дважды, и к моменту второго появления инструкции сложения в 047042
ЦП предписывается сложить содержимое слова в 102606 с содержимым сло-
ва в 104004 и оставить результат в 104004. Сложение дает 102000 + 003013 =
= 105013. Затем опять уменьшается содержимое ячейки 074010, на этот раз
до 3, и, поскольку этот результат не нулевой (бит Z сброшен), ветвление в
047054 опять происходит, а управление еще раз передается по адресу 047032.
Теперь мы располагаем циклом инструкций, состоящим из двух инструк-
ций увеличения, инструкции сложения и инструкции уменьшения. Результат
уменьшения проверяется с помощью инструкции ветвление-если-не-ноль, и
цикл повторяется вновь. Инструкция увеличения должна модифицировать ад-
ресную часть инструкции сложения, чтобы при каждом прохождении цикла
инструкция сложения оперировала со следующим словом — следующим из
пяти чисел (теперь должно быть очевидным, почему мы хотели расположить
эти пять чисел в последовательных словах памяти) . Выполнение цикла будет
повторяться до тех пор, пока пятое число не будет прибавлено к слову в ячейке
104004, а слово в ячейке 074010 после этого снова уменьшено. На этот раз
результат есть 0, и инструкция ветвление-если-не-ноль будет проигнорирова-
на. Таким образом, будет выполняться инструкция останова в ячейке 047056
и выполнение цикла прекратится, а сумма пяти чисел окажется в ячейке
104004.
Предшествующий пример показывает, как ’’безграмотный” ЦП распозна-
ет, какое слово оперативной памяти содержит данные, а какое инструкцию.
Слово в ячейке 047044 рассматривалось как данные двумя инструкциями
увеличения, а позднее это же слово интерпретировалось как часть некоторой
инструкции, т. е. адресная часть инструкции сложения. Теперь читателю дол-
жно быть понятно, что любое слово оперативной памяти, независимо от того,
мыслим ли мы его как данные или инструкцию, может обрабатываться ЦП
любым способом, который имеется в его распоряжении. При программиро-
вании на высоком уровне (например, на Бейсике или Паскале) это обычно
невозможно, поскольку данные и инструкции тщательно отделяются друг от
друга. Но на машинном уровне, как мы видим, такая манипуляция инструк-
циями возможна и порою является мощным средством программиста. Но
здесь всегда есть потенциальный риск, а иногда и скрытые опасности. В не-
скольких последующих примерах мы увидим, что такая модификация кода
инструкции может оказаться полезной при условии очень тщательного и вни-
мательного подхода.
Хотя читатель, вероятно, и испытывал небольшие трудности в прослежива-
76
нии логики этой программы (почему использовались эти конкретные ин-
струкции и почему был^выбран именно такой порядок их загрузки для вы-
полнения), он может также несколько недоумевать по поводу того, как мы
узнали, что такие инструкции реализованы на ЭВМ PDP-11, и как определили
коды инструкций, т. е. битовые конфигурации, которые должны быть деко-
дированы как увеличение, сложение и т. п. Здесь мы должны опираться на
материалы, поставляемые фирмами-производителями ЭВМ, в которых опи-
саны различные операции, способы их выполнения и их коды. Таким обра-
зом, если мы знаем, что на данной ЭВМ реализована операция уменьшения,
и хотим ее использовать, то можем найти в таблице 16-битовое слово, пред-
ставляющее эту инструкцию. По вопросам, касающимся логики самой про-
граммы, мы отсылаем читателя к гл. 6.
5.9. НЕКОТОРЫЕ ДРУГИЕ РЕГИСТРЫ ЦП
Мы потратили некоторое время на обсуждение двух общедоступных ре-
гистров ЦП (регистров, не являющихся внутренними для процессора), а
именно программного счетчика и слова состояния процессора. Очевидно,что
они весьма специализированы в смысле их поведения и использования. Но в
дополнение к ним ЦП располагает общедоступными (доступными извне) ре-
гистрами, которые не столь специализированы. В будущих примерах про-
грамм (фактически, в следующем разделе) мы увидим, что эти регистры мо-
гут использоваться для хранения счетчиков, адресов и промежуточных ре-
зультатов, для накопления сумм и произведений. Они оказываются очень
удобными для целого ряда различных целей. Эти регистры, в противополож-
ность PSW, не имеют адресов и, таким образом, для целей нашего обсужде-
ния мы просто присвоим им имена. Поскольку есть восемь таких регистров,
мы будем ссылаться на них как на регистры от R0 до R7.
R1
R2 [ , - .
R3 I (Регистры общего назначения)
R4
R5 J
R6
R7
Читатель, конечно, заметил, что первые шесть из них, от R0 до R5 включи-
тельно, помечены как регистры общего назначения, это значит, что они могут
использоваться для любых целей — как счетчики, аккумуляторы и т. д. По-
добно любому слову оперативной памяти, эти регистры есть 16-битовые сло-
ва, которые в арифметическом и других аспектах выступают точно так же,
как их партнеры в оперативной памяти, за исключением, как уже сказано,
того, что они не имеют числовых адресов. Регистр 6 представляет специаль-
ный случай, но не потому, что он своим поведением отличается от регистров
R0 — R5, а потому что ЦП специальным образом использует этот регистр. По
этой причине он не был включен в группу регистров общего назначения. Де-
тали использования R6 процессором даны в гл. 8.
В группу регистров общего назначения мы также не включили регистр R7
77
по той простой причине, что он является программным счетчиком, а мы те-
перь знаем, что R7 (PC) используется ЦП весьма специальным образом. Мы
опять предупреждаем читателя, чтобы он не думал, что есть что-то необычное
в поведении R7 (например, в арифметическом отношении) из-за того, что он
используется ЦП для указания на следующую инструкцию, подлежащую вы-
полнению. Регистр R7 может участвовать в арифметических операциях, и ин-
струкция условного ветвления-если-не-ноль фактически вовлекает его в та-
кую операцию и приводит к ожидаемым результатам. Однако очевидно, что
регистр R7 не может использоваться в качестве регистра общего назначения,
например для целей счета или аккумуляции. Поэтому в качестве общего пра-
вила мы можем сказать, что из восьми регистров от R0 до R7 регистры обще-
го назначения от R0 до R5 могут использоваться по желанию, а регистры R6 и
R7 играют специальную роль в отношении ЦП и без определенной глубины
понимания последствий (которым мы сейчас не располагаем) произвольное
их использование, вероятно, приведет к неприятностям.
5.10. ИСПОЛЬЗОВАНИЕ РЕГИСТРОВ ОБЩЕГО НАЗНАЧЕНИЯ
В этом разделе мы увидим, как регистры общего назначения могут исполь-
зоваться для различных целей, опять представив программу сложения, пока-
занную на рис. 5.8.1. Эта последняя версия дана на рис. 5.10.1.
Опять мы полагаем, что первоначально с (PC) = 047032. После извлечения
инструкция декодируется как ’’Поместить число 5 в RQ”, т. е. содержимое
16-битового регистра R0 устанавливается равным 000005. Следующая ин-
струкция (в ячейке 047036) аналогична, она предписывает поместить число
’ 102604 в регистр 2, и из листинга программы мы замечаем, что это число е.сть
адрес первого из пяти чисел, подлежащих сложению. Мы незамедлительно
увидим, как используется этот адрес. Следующая инструкция очищает ре-
Адрес Содержимое Комментарии
047032 012700 Поместить число 5 в регистр ф (R$)
047034 000005 для использования в качестве счетчика.
047036 012702 Поместить число 102604 (адрес первого числа.
047040 102604 подлежащего сложению) в регистр 2 (R2).
047042 005005 Очистить регистр 5 (т. е. установить с (R5) в 0)
047044 062205 Инструкция сложения (поясненная в тексте).
047046 005300 Уменьшить содержимое R0.
047050 001375 Ветвление-если-не-ноль в ячейку 047044.
047052 000000 Остановить ЦП.
102604 003013 Это
102606 102000 есть
102610 000003 числа,
102612 012340 подлежащие
102614 100114 сложению.
Рис. 5.10.1
78
гистр R5, т. е. устанавливает содержимое R5 равным 0. Именно здесь будет
накапливаться сумма чисел и, как и в предыдущей программе, это слово ини-
циализируется в 0. Теперь мы готовы начать сложение чисел в их аккумуля-
торе R5.
Инструкция сложения в ячейке 047044, а именно 062205, отличается по
форме от аналогичной инструкции в предыдущей версии, а ее действия тре-
буют некоторых пояснений. Действие этой инструкции состоит в сложении
содержимого ячейки памяти, адрес которой находится в R2, с содержимым
R5 и в помещении результирующей суммы назад в R5. Содержимое R2 есть
102604, и это число берется как адрес ячейки памяти, содержимое которой
должно прибавляться к текущему содержимому R5. Таким образом, дей-
ствие состоит в сложении с( 102604) cc(R5) и помещении результата, т. е.
003013 + 000000 = 003013, в R5. Так мы прибавили первое число к c(R5).
Но инструкция фактически делает несколько большее. Когда ЦП осущест-
вляет доступ к содержимому R2 для получения адреса 102604, он также уве-
личивает c(R2) на 2, так что теперь мы имеем c(R2) = 102606. Значение это-
го действия состоит в том, что регистр R2 теперь указывает на второе из пяти
чисел (содержит его адрес). Важно, чтобы читатель понимал, что увеличение
c(R2) не явилось результатом какой-то отдельной инструкции, а обусловле-
но формой инструкции сложения 062205. Детальные пояснения будут да-
ны в гл. 7.
Точно так же, как в предыдущей версии, счетчик (инициализированный
как 5) теперь уменьшается (в ячейке 047046), хотя в этом случае счетчик
располагается в R0, а не в слове оперативной памяти. Проверка бита Z в
PSW осуществляется инструкцией ветвление-если-не-ноль, и управление, как
читатель может убедиться, передается назад, к ячейке 047044. Мы вернулись
к инструкции сложения и, как прежде, она использует с (R2) как адрес ячей-
ки, содержимое которой должно быть прибавлено к c(R5). Но обратите вни-
мание, что из-за увеличения (на 2), которое произошло, когда это сложение
встретилось последний раз, мы теперь имеем c(R2) = 102606 и, таким обра-
зом, содержимое ячейки 102606 (а именно 102000) прибавляется к текуще-
му содержимому регистра R5 (003013). Поэтому, как и прежде, R5 теперь
содержит сумму первых двух чисел. Дополнительным результатом этой фор-
мы операции сложения является то, что c(R2) опять увеличивается на 2, в ре-
зультате чего мы теперь имеем c(R2) = 102610, т. е. адрес третьего числа. За-
тем уменьшается c(R0), проверяется бит Z, происходит ветвление в 047044
и т. д. до тех пор, пока не будет прибавлено последнее число к c(R5). Затем
c(R0) уменьшается до 0, так что ветвление не происходит, процессор останав-
ливается, а сумма пяти чисел находится в R5.
Своеобразная инструкция сложения, использованная здесь, требует даль-
нейших исследований, которые будут сделаны в гл. 7. Пока же читателю
следует взять на заметку желательное свойство — увеличение содержимого
регистра R2 на 2 каждый раз при обращении для получения адреса одного из
слагаемых. Как мы видели, эффект состоит в том, что R2, который первона-
чально указывал на первое число, устанавливается затем так, что указывает на
второе число, затем на третье и т. д. Это ’’пошаговое прохождение по последо-
вательным ячейкам памяти” имеет очень большое значение для структуры
программы, поскольку исключает необходимость явного увеличения (дваж-
79
ды) адресной части инструкции сложения. Фактически мы видим, что эта по-
следняя версия не модифицирует какую-либо из своих инструкций.
И наконец, отметим, что эта версия физически короче, чем представлен-
ная на рис. 5.8.1. Например, инструкция сложения здесь однословная, в про-
тивоположность трехсловной, необходимой ранее. Вообще говоря, исполь-
зование регистров общего назначения обычно дает в результате более корот-
кие, более эффективные программы.
5.11. УПРАЖНЕНИЯ
5.7.1. Предположим, что каждая из следующих инструкций ветвление-если41е-ноль
помещена в ячейке 020332. Найдите адрес ячейки, в которую передается управление,
если ветвление происходит (т. е. если в момент декодирования инструкции бит Z в
PSW сброшен).
а) 001023 г) 001176 ж) 001376
б) 001014 д) 001276 з) 001377
в) 001100 е) 001200 и) 001000
5.7.2. Предположим, что инструкция ветвление-если-не-ноль размещена в ячейке
020332. Постройте (в шестицифровом восьмеричном формате) инструкцию для каждо-
го ’’целевого” адреса из адресов, представленных ниже (т. е. для адреса, в который дол-
жно быть передано управление, если бит Z в PSW сброшен).
а) 020604 г) 012606 ж) 020332
б) 020340 д) 020334 з) 024336
в) 020302 е) 020000 и) 020330
5.7.3. Предположим, что в ячейке 003674 мы намереваемся разместить инструкцию
’’ветвление-если-не-ноль в ячейку 003274”.
а) Покажите, что ограничение размера пословного смещения в инструкциях услов-
• ного ветвления не дает возможности осуществить такую инструкцию ветвление-если-не-
ноль.
б) Покажите, однако, что следующие инструкции приводят к тому же самому ре-
зультату :
Адрес Содержимое
003674 001402
003676 012707
003700 003274
003702 —
(Инструкция, код которой равен 001402, есть условное ветвление, означающее вет-
вление-если-рав ио-нулю, а комбинация
012707
003274
образует инструкцию ’’Поместить число 003274 в программный счетчик”.)
5.7.4. Какими будут последствия для выполнения инструкций, вызванные инструк-
цией, помещенной в 005314, действие которой состоит в помещении числа 005314 в
программный счетчик?
5.7.5. В разд. 5.7 утверждалось, что сдвиг битов байта на одну позицию влево созда-
ет эффект удвоения его значения. Объясните, почему это происходит при условии, ко-
гда бит 7 содержит 0. Что случится, если бит 7 содержит 1?
5.8.1. Предположим, что вторая программа из разд. 5.8 (сложение пяти чисел) загру-
жена в оперативную память так, как показано на рис. 5.8.1. Предположим теперь, что
эта программа начинает выполняться, т. е. число 047032 загружается в PC и процессор
запускается. Тогда пять чисел будут сложены, сумма помещена в ячейку 104004, а про-
цессор остановится. Теперь предположим, что мы заново запускаем программу, просто
80
перезагрузив PC числом 047032 и опять запустив процессор. Слова оперативной памяти
мы специально не перезагружаем. Покажите, что второе выполнение данного кода не
приведет опять к сложению этих пяти чисел. Определите точно, где возникают трудности
(их три)'. И, наконец, объясните, что будет происходить при этом повторном выполнении.
5.10.1. Обращаясь к упражнению 5.8.1, объясните, почему вторая версия программы
сложения, показанная на рис. 5.10.1, может быть выполнена повторно с ожидаемыми ре-
зультатами. ,(Упражнения 5.8.1 и 5.10.1 демонстрируют те губительные последствия, к
которым порою приводят инструкции, модифицирующие другие инструкции, и про-
граммы, не способные инициализировать различные ячейки памяти перед их использо-
ванием.)
5.10.2. Было отмечено, что инструкция сложения в первой версии программы сложе-
ния была трехсловной, тогда как во второй версии требовалось только одно слово.
Объясните, куда ’’сбежали” два других слова.
ГЛАВА 6. АССЕМБЛИРОВАНИЕ (ПОСТРОЕНИЕ) ПРОГРАММЫ
6.1. СОЗДАНИЕ ПРОГРАММЫ
Приступая к написанию программы, мы должны пройти через ряд (зача-
стую неформальных) фаз. Прежде всего мы должны быть уверены, что пол-
ностью понимаем предстоящую работу и то, какую информацию имеем в
своем распоряжении. Затем мы разрабатываем основной подход, решая, на-
пример, что работа может быть выполнена несколькими методами, и выби-
рая в конце концов наиболее нам приглянувшийся, вероятно, на основании
его эффективности, понятности или выгоды. Затем мы предпринимаем более
формальное наступление на проблему, когда записываем алгоритм (или про-
цедуру) ее решения. Именно на этой стадии вводятся машинные и языковые
соглашения. Если мы решаем написать программу, например, на языке Пас-
каль, то ограничения и особенности этого языка в определенной степени влия-
ют на процедуру. Теперь наступает время записи алгоритма в выбранном язы-
ке, загрузки результирующего кода в оперативную память и его выполнения.
При написании программы на языке высокого уровня, таком как Бейсик,
Фортран или Паскаль, эти последние шаги не представляют трудности, по-
скольку существо дела заключается в построении алгоритма; после констру-
ирования алгоритма трансляция на конкретный язык представляется не-
сложной.
Описанные выше фазы программирования в равной степени относятся к
программистам, которые пишут программы на машинном языке — внутрен-
нем языке самой ЭВМ (несколько небольших программ, с которыми мы по-
знакомились до сих пор, являются программами машинного языка). И хотя
эта последняя фаза — запись алгоритма на машинном языке — может пред-
ставляться несложной, именно здесь возникают многочисленные трудности.
Написание программ на машинном языке — очень трудная задача, требующая
много времени и хорошей подготовки.
6.2. ОБРАЗЕЦ ПРОГРАММЫ
Несколько программ на машинном языке, с которыми мы имели дело др
сих пор, были представлены как полные программы; различные их особен-
81
ности были рассмотрены с определенной степенью детализации. В этом разде-
ле мы ответим на вопрос о том» каким образом создаются такие программы,
т. е., прежде всего, как конструируются инструкции, образующие програм-
мы. Эти процедуры мы проиллюстрируем на примере одной концептуально
простой проблемы.
В качестве иллюстративного примера используем задачу нахождения на-
ибольшего числа из набора чисел со знаком. Мы пройдем различные фазы,
описанные в предыдущем разделе, и подробно познакомимся с процессом
написания программы. Во-первых, при заданных числах, среди которых дол-
жно быть найдено максимальное, мы знаем, что предстоит сделать, и облядя-
ем для этого достаточной информацией. Ради определенности обсуждения
предположим, что мы имеем дело с пятью числами, хотя легко увидеть, что
процедура не зависит от фактического их числа. Во-вторых, мы решаем при-
держиваться следующего подхода к проблеме. Находим первое из пяти чи-
сел и незамедлительно объявляем его наибольшим. Вероятно, бол ее,подхо-
дящим является термин временный максимум. Теперь мы по очереди про-
сматриваем оставшиеся четыре числа (5-1), сравнивая каждое с этим
временным максимумом. Если встречаем число большее, чем временный
максимум, то заменяем временный максимум этим числом и продолжаем
процесс. К моменту завершения проверки оставшихся четырех чисел времен-
ный максимум фактически будет максимальным из пяти чисел.
Запишем процедуру на русском языке в виде более формального алгорит-
ма, присвоив символические имена некоторым программным компонентам.
1. Установить COUNTER рав-
ным 4.
2. Установить ТЕМРМАХ рав-
ным первому числу.
3. Получить следующее число и
назвать его NUM.
4. Если NUM меньше или рав-
но ТЕМРМАХ, перейти к
шагу 6.
5. Установить ТЕМРМАХ рав-
ным NUM.
6. Уменьшить COUNTER на 1.
7. Если COUNTER не нулевой,
вернуться к шагу 3.
8. STOP
Хотя имеются пять чисел, но,поскольку пер-
вое число будет временным максимумом,
для проверки остаются четыре числа.
Принять первое число как временный мак-
симум.
Просмотреть по очереди каждое число.
Сравнить число с временным максимумом.
Если оно не больше, то пропустить шаг 5.
Заменить временный максимум большим
числом.
Уменьшить счетчик, чтобы увидеть, есть ли
еще числа для сравнения.
Поскольку есть еще числа для сравнения,
получить следующее число.
Процедура завершена, и наибольшее из пяти
чисел находится в ТЕМРМАХ.
Теперь необходимо принять несколько решений по поводу переноса про-
цедуры на машинный язык. Нам необходим счетчик с первоначальным зна-
чением 4, и наиболее вероятным кандидатом для этого является регистр (на-
пример, R0). Сами пять чисел мы поместим в последовательных ячейках па-
мяти, и наш опыт работы с программой сложения в разд. 5.10 показывает,
что эффективный способ обращения к этим числам состоит в том, что адрес
82
первого числа помещается в некоторый регистр, с помощью которого мы за-
тем шаг за шагом по очереди будем проходить по адресам всех чисел. Для
этой цели мы выбираем R2 и устанавливаем его содержимое первоначально
равным адресу первого числа. Хотя мы приняли решение о размещении этих
пяти чисел в последовательных ячейках памяти (обратите внимание, что если
бы числа не находились в последовательных ячейках, мы не смогли бы вос-
пользоваться преимуществом пошагового прохождения по адресам с помо-
щью R2), мы ничего еще не сказали о том, где будут расположены эти ячейки
памяти. Хотя нам уже известно, что они могут находиться в любом месте опе-
ративной памяти, мы полагаем, что пять чисел будут размещены в оператив-
ной памяти сразу же вслед за инструкциями программы. И, наконец, нам не-
обходимо какое-то место для хранения временного максимума (в алгорит-
ме — ТЕМРМАХ), и для этой цели мы выбираем R5. Выбор различных регист-
ров для хранения программных значений совершенно произволен за исклю-
чением, конечно, того, что если регистр уже был предназначен для одной це-
ли, то не может использоваться для любой другой.
Теперь, когда мы присвоили некоторым регистрам определенные началь-
ные значения, давайте неформально пройдем по процедуре. В начале, мы пе-
ресылаем число, адрес которого находится в R2 (т. е. первое из пяти чисел),
в R5 в качестве временного максимума. Затем мы должны увеличить c(R2)
на 2 так, чтобы R2 содержал адрес второго числа. Теперь мы готовы к выпол-
нению сравнения. Если число, адрес которого находится в R2 (второе из пя-
ти чисел), больше, чем число в R5 (временный максимум), то оно должно за-
менить временный максимум, т. е. число, адрес которого находится в R2,
должно быть переслано в R5. Если нет, то этот последний шаг должен быть
пропущен. Затем мы уменьшаем значение счетчика R0H, если он теперь не ну-
левой, программа должна возвратить управление в ту точку, в которой с (R2)
было увеличено на 2 с тем, чтобы R2 теперь содержал адрес следующего чис-
ла. Но если значение счетчика (R0) равно 0, то программа должна остановить-
ся — максимальное число будет находиться в R5.
Определив процедуру (где будут храниться различные счетчики, числа и
т. п. и в некотором отношении даже какие будут использоваться машинные
инструкции) , мы теперь готовы приступить к написанию инструкций. На пер-
вом шаге надо решить, в какое место оперативной памяти программа будет
загружаться. Мы, конечно, весьма далеки от реальной загрузки инструкций
и данных в память, но, как увидим, для некоторых инструкций требуются
фактические адреса в памяти, и нам нужно будет знать, каковы эти адреса.
Поскольку оперативная память обладает свойством прямого доступа, ника-
кое конкретное место в ней не может быть предпочтительнее другого, поэто-
му начнем с самого первого слова памяти — с ячейки с адресом 000000. Вспо-
минаем, что первое, что необходимо сделать, — зто инициализировать счет-
чик (R0), присвоив ему значение 4. При поиске машинной инструкции для
’’пересылки абсолютного числа 4 в регистр 0” мы обнаруживаем, что это
двухсловная инструкция, в которой первое слово представляет код опера-
ции пересылки (012700), а второе — это само число 4 (000004). Таким обра-
зом, мы можем записать следующее (как обычно, первое число представляет
адрес, используемый для ссылок, а второе число — содержимое этого адреса):
000000 012700 Переслать число
83
000002 000004 4 в регистр R0
Все идет успешно, пока мы не попытаемся записать следующую инструк-
цию. Вспомним о нашем решении, что адрес первого из пяти чисел должен по-
мещаться в R2. Хотя мы в состоянии отыскать код для инструкции ’’Пере-
слать число (адрес) в R2”, к несчастью, в этом месте мы не знаем, какое чис-
ло должно быть переслано в R2. По предположению оно является адресом, но
в настоящее время мы не можем заглянуть вперед, чтобы узнать точно, где
(по какому адресу) будет в конце концов загружено первое число. Таким
образом, мы не можем завершить эту инструкцию, хотя нам известно, что
она состоит из двух слов, первое из которых есть оператор ’’переслать абсо-
лютное число в R2” (код которого есть 012702), а второе слово представля-
ет неизвестный адрес. Лучшее, что мы можем сделать, это записать инструк-
цию частично и отметить в уме, что она должна быть завершена позднее, ко-
гда станет известен адрес первого из пяти чисел. Таким образом, теперь мы
имеем:
000000 012700
000002 000004
000004 012702 Переслать (неизвестный) адрес
000006 ----- первого числа в регистр 2.
Неприятность этого типа не столь уж велика, поэтому мы следуем даль-
ше. Полагая теперь, что во время выполнения программы адрес первого чис-
ла будет находиться в R2, мы хотим переслать первое число в R5 в качестве
временного максимума. Процесс, который мы используем здесь (при поша-
говом прохождении с помощью R2 по адресам пяти чисел), напоминает про-
грамму сложения на рис. 5.10.1. Вспоминаем, что в той программе регистр
R2 содержал адреса чисел, подлежащих сложению, и по мере прибавления
каждого числа содержимое R2 автоматически увеличивалось на 2, так что в
нем всегда находился адрес следующего слагаемого. В том случае автомати-
ческое увеличение было очень желательно, но здесь мы предпочитаем этого
не делать по причинам, которые вскоре станут ясными. Таким образом, нам
необходим некоторый вариант оператора пересылки, транспортирующий
число, адрес которого находится в R2, в регистр R5, никак при этом не влияя
на значения в R2. Отыскиваем такой оператор и добавляем его к программе.
000000 012700
000002 000004
000004 012702
000006 -------
000010 011205 Переслать число, адрес которого находится в R2, в
R5 (без изменения значения содержимого R2).
Теперь, когда мы придали первоначальное значение временному максиму-
му (R5), можно приступить к сравнению его с остальными четырьмя числа-
ми. Но в R2 находится адрес первого числа, тогда как он должен быть адре-
сом второго числа. Здесь как раз подходящее место, чтобы прибавить 2 (од-
ному слову эквивалентны два байта) к содержимому R2. Поскольку суще-
ствует инструкция для увеличения c(R2) на 1, нам необходимо только вклю-
чить здесь две копии этой инструкции.
84
000000 012700
000002 000004
000004 012702
000006 -------
000010 011205
000012 005202 Увеличить содержимое
000014 005202 R2 (на 1) дважды.
Теперь мы готовы сравнить число в R5 (временный максимум) с числом,
адрес которого находится в R2. Поскольку есть инструкция ЭВМ PDP-11, вы-
полняющая точно то, что нам нужно, мы вставляем ее код в этом месте про-
граммы.
000000 012700
000002 000004
000004 012702
000006 -------
000010 011205
000012 005202
000014 005202
000016 020512 Сравнить содержимое R5 с содержимым ячейки па-
мяти, адрес которой находится в R2.
По поводу оператора сравнения необходимо дать некоторые пояснения.
Этот оператор вычисляет разницу между первым указанным числом (в нашем
случае временным максимумом в R5) и вторым числом (числом в ячейке
памяти, адрес которой находится в R2) и соответствующим образом устанав-
ливает коды условий в PSW. Ни на одно из самих чисел это вычитание не вли-
яет, только на коды условий.
Как дальше развивается логика программы? Если результат ’’вычитания”
в инструкции сравнения положительный, то это означает, что содержимое R5
(временный максимум) больше, чем число, сравниваемое с ним, так что вре-
менный максимум заменяться не должен. Замены временного максимума не
требуется даже и тогда, когда результатом сравнения является 0, означаю-
щий равенство двух чисел.
Замена временного максимума должна быть сделана только в том случае,
если результат вычитания в инструкции сравнения отрицательный. Таким об-
разом, за инструкцией сравнения должна следовать инструкция условного
ветвления для обхода (чего-то), если результат сравнения больше или равен
0. То, что должно быть обойдено, это, конечно, инструкция или инструкции
для замены временного максимума (в R5) числом, адрес которого находит-
ся в R2. Поскольку существует инструкция условного ветвления вида ”вет-
вление-если-больше-или-равно-нулю”, нам следует здесь ее вставить. Как и во
всех инструкциях условного ветвления, младший байт этой инструкции со-
держит пословное смещение со знаком. Но сколько слов надо пропустить? В
этом месте программы трудно предвидеть, куда должно происходить ветвле-
ние, поэтому в настоящий момент мы не можем полностью завершить ин-
струкцию ветвления. И опять, лучшее, что мы можем сделать, это заметить,
что инструкция будет занимать одно слово и его нужно будет заполнить позд-
нее, когда станет известно, куда осуществлять ветвление.
85
000000 012700
000002 000004
000004 012702
000006 —— —— — ——
000010 011205
000012 005202
000014 005202
000016 020512
000020 Ветвление (куда?), если результат сравнения был больше или равен 0.
Несмотря на эту проблему, пойдем дальше и построим код для замены
временного максимума. Вспоминая, что адрес наибольшего числа {нового
временного максимума) находится в R2, видим, что нам необходим некото-
рый вид оператора пересылки, транспортирующий число, адрес которого на-
ходится в R2, в регистр R5. Его код — 011205. Теперь должно быть ясно, по-
чему мы хотели обрабатывать увеличение содержимого R2 самостоятельно
(путем двойного увеличения в ячейках 000012 и 000014), а не использовать
какой-либо тип автоувеличения. Если бы c(R2) подвергалось автоувеличению,
то теперь R2 содержал бы адрес следующего из пяти чисел, а не числа, кото-
рое должно быть переслано в R5. Теперь наша программа выглядит так:
000000 012700
000002 000004
000004 012702
000006 —
000010 011205
000012 005202
000014 005202
000016 020512
000020 —
000022 011205
Переслать число, адрес которого
находится в R2, в регистр R5.
Сегмент кода, который увеличивает содержимое R2, сравнивает его с
c(R5) и, если необходимо, заменяет, теперь завершен. Настало время под-
строить счетчик (уменьшить его значение) в регистре R0 и, если еще не все
числа были рассмотрены, перейти назад (куда-то) для повторного выполне-
ния этих инструкций. Находим и вставляем в частично построенную програм-
му инструкцию для уменьшения счетчика в регистрах R0.
000000 000002 000004 000006 000010 000012 000014 000016 000020 000022 86 000024 012700 000004 012702 011205 005202 005202 020512 011205 005300 Уменьшить содержимое R0.
Теперь, когда значение счетчика уменьшено, мы должны перейти назад (ку-
да-то в программе) при условии, что счетчик не равен 0. Необходимо только
определить, куда мы хотим сделать ветвление, так что мы вычисляем послов-
ное смещение, которое должно помещаться в младшем байте инструкции
ветвления.
Вспоминаем, что мы только что сравнили содержимое R5 с числом, адрес
которого находится в R2 и, поскольку мы не разрешили автоматическое,
увеличение содержимого R2, он все еще содержит адрес того же числа. Для
выполнения следующего сравнения R2 должен, конечно, содержать адрес сле-
дующего числа. Поэтому c(R2) должно быть увеличено на 2. Бросив взгляд
на программу, мы видим, что желаемая подстройка произойдет, если мы про-
сто вернемся назад к ячейке 000012 — содержимое R2 будет увеличено на 2,
произойдет следующее сравнение и т. д. Чтобы определить, какой должна
быть инструкция ветвление-если-не-ноль, необходимо знать следующее. Во-
первых, эта инструкция занимает одно слово, которое находится в ячейке
памяти 000026. Во-вторых, ее младший байт содержит число слов (со зна-
ком) , на которые надо подстроить PC. В-третьих (вероятно, это наиболее ре-
шающий момент), когда программа выполняется (опять напоминаем читате-
лю, что мы весьма далеки от фазы выполнения) , ЦП извлекает эту инструк-
цию ветвления и, следовательно, увеличивает с(РС) до 000030, и именно это
значение должно быть подстроено для достижения ветвления в 000012. Для
получения 000012 из 000030 нужно вычесть 000016. Таким образом, смеще-
ние равно —16 байтам, или —7 словам (вспомним, что нам требуется послов-
ное смещение). Поскольку в младшем байте инструкции ветвления должно
помещаться восьмибитовое со знаком число —7 = 371 и поскольку старший
байт оператора ветвление-если-не-ноль есть 002, мы можем объединить эти
байты и получить:
00000010 11 111 001 =001371
Теперь мы чувствуем себя свободно, как дома. Мы только что определили
код, который должен быть помещен в ячейку 000026, и знаем, что, если ин-
струкция ветвления не вызывает перехода, программа завершается и мы про-
сто должны использовать инструкцию останова HALT, код которой есть
000000. Таким образом, необходимо загрузить 000000 в слово, следующее
за инструкцией условного ветвления (а именно в 000030). И, наконец, мы
можем поместить данные (пять чисел, максимальное из которых должно
быть найдено), начиная с ячейки 000032. Наша (почти) полная программа
показана на рис. 6.2.1.
Осталась только одна задача — заполнить места, отмеченные штриховыми
линиями. Первое место заполнить легко. Вспоминаем, что слово в ячейке
000006 мы оставили пустым, поскольку это адрес первого из пяти чисел, а
мы не могли предвидеть, каким он будет. Теперь мы видим, что зто 000032,
и можем поместить это значение в ячейку 000006. Штриховая линия в ячейке
000020 представляет собой инструкцию ветвления, предназначенную для об-
хода инструкции, с помощью которой заменяется временный максимум. То
есть, если результат сравнения, выполняемого инструкцией в ячейке 000016,
больше или равен 0, мы не хотим выполнять инструкцию пересылки в ячейке
000022. Вместо этого мы должны перескочить в ячейку 000024 — к инструк-
ции уменьшения содержимого R0. Чтобы вычислить пословное смещение, по-
87
000000 012700
000002 000004
000004 012702
000006
000010 011205
000012 005202
000014 005202
000016 020512
000020
000022 011205
000024 005300
000026 001371 Ветвление назад к 000012, если результат
уменьшения содержимого Кф был
ненулевым.
000030 000000- Остановить ЦП
000032 000403 Числа,
000034 102304 максимум
000036 023704 которых
000040 007702 должен быть
000042 176337 найден.
Рис. 6.2.1
000000 012700 Переслать число 4
000002 000004 в Яф в качестве счетчика.
000004 012702 Переслать число (адрес)
000006 000032 000032 в R2.
000010 011205 Переслать число, адрес которого находится в R2, в R5.
000012 005202 Уменьшить содержимое R2
000014 005202 дважды.
000016 020512 Сравнить содержимое R5 с числом, адрес которого находится в R2.
000020 002001 Ветвление к 000024, если результат сравнения был больше или равен нулю.
000022 011205 Переслать число, адрес которого находится в R2, в R5.
000024 005300 Уменьшить содержимое R<£>.
000026 001371 Ветвление к 000012, если результат уменьшения R0
был ненулевым.
000030
000032
000034
000036
000040
000042
000000
Остановить ЦП.
Пять чисел, максимальное из которых должно
быть найдено, загружаются здесь.
Рис. 6.2.2
88
мещаемое в младшем байте инструкции ветвление-если-больше-или-равно-ну-
лю, необходимо знать, что при выполнении программы к моменту извлече-
ния инструкции содержимое программного счетчика будет увеличено до
000022. Поскольку мы хотим сделать условное ветвление в ячейку 000024,
очевидно, что смещение будет составлять два байта или одно слово. Таким
образом, младший байт инструкции должен содержать 001 и, поскольку стар-
ший байт инструкции ветвление-если-больше-или-равно-нулю есть 004, мы мо-
жем объединить эти байты и получить 002001; это слово должно быть поме-
щено в ячейку 000020. Программа теперь полностью завершена, и ее оконча-
тельный вид с комментариями показан на рис. 6.2.2.
Теперь конструирование программы закончено, и мы можем загрузить ее
инструкции и данные в оперативную память, начиная с ячейки 000000, уста-
новите содержимое PC равным 000000, поскольку хотим начать выполнение
именно с этого места, и запустить ЦП. Делая так, мы очень надеемся, что про-
грамма заработает правильно с первого раза. Однако, если при разработке
логики или в некоторых наших вычислениях что-то окажется неверным, то
могут потребоваться исправления. Даже если исправления незначительны по
смыслу, может потребоваться внесение или удаление слова или двух в ка-
ком-либо месте программы. Но, делая это, мы изменяем адреса программы.
В частности, даже при небольших модификациях программы мы можем из-
менить адреса и смещения, являющиеся частью инструкций, которые затем
также потребуется исправлять, модифицировать, заменять и т. п. Таким обра-
зом, даже малые изменения (’’малые” на первый взгляд) могут потребовать
значительной переделки.
Разрабатывая программу в этом разделе, мы значительно углубились в
детали, но, если присмотреться, видно, что все эти детали были абсолютно
необходимы. Теперь читатель может задаться вопросом, действительно ли
сложно и тяжело писать программы на машинном языке. Ответом будет — да.
В действительности дело обычно обстоит намного хуже. В конце концов, на-
ша программа-образец - весьма простая и очень короткая. Проблемы, с ко-
торыми сталкивается программист, приводят к программам длиной в сотни
и даже тысячи инструкций. Тем, кто впал в уныние, можем сказать, что по-
мощь близка, - читатель найдет ее в следующих четырех разделах.
В этом месте может возникнуть законный вопрос: почему мы пытаемся
даже простые программы писать на машинном языке, когда нам доступны
удобные и легкие для изучения языки, такие как Бейсик, Фортран и т. п.?
Есть много ответов, но мы предлагаем два. Сначала вспомним, что нашей ос-
новной целью является исследование работы и возможностей современной
ЭВМ; набор машинных инструкций и то, как они работают, — это централь-
ные вопросы архитектуры ЭВМ. Поэтому мы разными способами будем ис-
пользовать машинный язык для иллюстрации и углубления понимания мно-
гих аппаратных особенностей вычислительных машин вообще и PDP-11 в
частности. Читателю также следует знать, что языки высокого уровня (на-
пример, Фортран или Кобол), хотя и удобны для программиста, обладают
определенной неэффективностью. Это вызвано тем, что программы, написан-
ные на них, зачастую занимают значительно больший объем оперативной па-
мяти, чем соответствующие программы, написанные на машинном языке
(правда, некоторые недавно разработанные языки намного более эффектив-
89
ны в этом отношении, чем упомянутые). Поэтому типичные программы на
языке высокого уровня не только используют больший объем Памяти, но и
имеют тенденцию к более медленному выполнению из-за необходимости об-
работки большего числа инструкций. Из-за этого большие программы, кото-
рые подлежат частому использованию, порою пишут на естественном машин-
ном языке с целью экономии места в оперативной памяти и сокращения вре-
мени выполнения.
6.3. МНЕМОНИЧЕСКИЙ ЯЗЫК
Пройдя кропотливое конструирование инструкций, загрузку и выполне-
ние программы предыдущего раздела (между прочим, она работает правиль-
но), сделаем небольшую паузу, чтобы проанализировать процесс, и, в частно-
сти, зададимся вопросом, что же в этой процедуре делает написание програм-
мы на этом уровне столь трудным. Отдельные фазы, по-видимому, не порож-
дают реальных проблем. Сначала мы приняли решение об алгоритмическом
подходе к проблеме нахождения максимального из набора чисел, что являет-
ся концептуально простой задачей. Затем мы записали специфические особен-
ности этого алгоритма, приняли несколько решений о назначении регистров и
написали реальную программу. На этой последней фазе мы обнаружили, что
необходимо находить коды для многочисленных машинных инструкций,
предвидеть некоторые адреса (а скорее, откладывать их определение) и вы-
числять несколько смещений. Ничто из этого само по себе не представляет
трудностей. Однако, когда эти детали вторгаются в логику программы, мы
легко теряем нить рассуждений о тбм, что пытаемся делать, — разработать
последовательность машинных инструкций, дающую конкретный результат.
Мы постоянно отвлекаемся от логического развития программы, так как вы-
нуждены заниматься деталями. Например, к моменту достижения в програм-
ме предыдущего раздела точки, где должен быть уменьшен счетчик, мы, ве-
роятно, уже забыли не только, какой это был регистр, но и какова была его
функция. Таким образом, нам приходится постоянно возвращаться назад не
только для того, чтобы вспомнить, какие регистры и ячейки памяти исполь-
зуются и для каких целей, но и для восстановления представления о самой
логике программы.
Необходимость останавливаться для поиска кодов, вычисления смещений
и т. п. не только удлиняет процесс программирования, но также увеличивает
вероятность ошибок с сопутствующими им исправлениями. Мы могли бы
избежать этого, если бы имели возможность просто писать инструкции, не
заботясь об остальных деталях. Конечно, если мы надеемся когда-нибудь вы-
полнить программу, то в конце концов должны познакомиться с деталями,
но было бы желательно разделить эти две задачи — написание самой програм-
мы и поиск кодов и адресов, вычисление сдвигов и т. п. Первая задача пред-
ставляется творческой, вторая же — это просто поденная работа. Для дости-
жения такого разделения процесса создания программ и ’’построения кода”
(который и есть действительно выполняемая программа) большинство про-
граммистов изобретает свои собственные ’’машиноподобные” языки. Они
используют минислова, или мнемоники (от греческого слова mnemonikos,
что означает ’’запоминать”), для обозначения машинных инструкций. Таким
образом, если, например, в некоторой точке программы необходимо пере-
слать содержимое регистра 1 в регистр 4, то мы могли бы написать
MOVR1.R4
вместо того, чтобы искать код (010104) для этой инструкции. Конечно, в
конце концов нам придется найти код для этой мнемонической инструкции,
но смысл заключается в том, что нам не нужно искать его в настоящий мо-
мент. Мы можем просто записать эту мнемонику и перейти к следующей
инструкции. И, конечно, если придется обращаться к этой инструкции позже,
то мнемоника MOV, Rl, R4 будет для нас иметь больший смысл, чем машин-
ный код 010104.
Безусловно, в дополнение к MOV нам потребуется изобрести минислова
для других инструкций. Например, существует инструкция, устанавливаю-
щая ячейку памяти или регистр в 0, и мы можем обозначить ее словом
CLEAR, или, более простым, CLR. Для инструкции ветвление-если-не-ноль
мы могли бы, естественно, использовать обозначение BNZ (Branch-if-Non-
Zero) , хотя стандартная мнемоника в ЭВМ PDP-11 для этой инструкции вет-
вления есть BNE (Branch-if-Not-Equal-to-zero). Аналогично BGE может обо-
значать инструкцию ветвление-если-больше-или-равно-нулю (Branch-if-Grea-
ter-than-or-Equal-to-zero). Другими возможными мнемониками являются:
для инструкции увеличения - INC (INCrement); для инструкции сравнения —
СМР (СоМРаге) ; для инструкции уменьшения - DEC (DECrement); для ин-
струкции останова процессора — HALT. Важно понимать, что использованные
нами мнемоники не даны свыше — мы выбираем их для того, чтобы облег-
чить запоминание. Другой программист мог бы предпочесть XFER (transfer),
а не MOV, ZERO, а не CLR, СОМ, а не СМР и т. д.; итак, быть по сему - клю-
чевым моментом должно быть удобство для программиста.
Разрабатывая эти мнемоники, мы должны сделать больше, чем просто при-
думать минислова для представления инструкций. Мы уже видели, что ряд
инструкций появляется в разнообразных формах, или режимах. Например,
мы можем переслать содержимое одного регистра в другой регистр или рас-
сматривать содержимое регистра как адрес и пересылать куда-то содержимое
этого адреса, например в регистр или другую ячейку памяти. В каждом слу-
чае основной инструкцией будет MOV, но использоваться она будет несколь-
ко по-разному. Фактически мы использовали эту инструкцию несколько по-
иному в качестве первой инструкции программы предыдущего раздела, ко-
гда пересылали число 4 в регистр R0. В дополнение к тому, что мнемоника
MOV предназначена для обозначения инструкции, которая пересылает слова
из одного места в другое, мы должны различать и эти разнообразные режимы
использования с помощью нашей нотации.
Примем следующую нотацию. Если мы пересылаем содержимое одного ре-
гистра в другой регистр, то будем просто записывать обозначения регастра-
источника (регистра, из которого поступают данные), и регистра-приемника
(регистра, куда данные пересылаются), разделяя их запятой. Таким образом,
MOV R5,R2
служит заменой фразы ’’Переслать содержимое регистра R5 в регистр R2”.
Машинный код этой инструкции — 010502, и мы знаем, что действие ее состо-
ит в копировании данных из R5 в R2. Такое копирование, или пересылка, не
91
90
влияет на содержимое регистра-источника R5, так что после пересылки оба
'регистра будут иметь одно и то же содержимое. Но если мы хотим, например,
переслать содержимое некоторой ячейки памяти в регистр, то в качестве ис-
ходного операнда просто используем мнемонический адрес этой ячейки. Сле-
довательно,
MOV 102334, R3
означает ’’переслать копию содержимого ячейки памяти с адресом 102334 в
регистр R3 без воздействия на содержимое ячейки 102334”.
Предположим теперь, что мы хотим переслать абсолютное число, такое
как 4, в какой-то регистр. Из-за только что принятого решения мы не можем
написать
MOV 4, R0
поскольку это означает ’’переслать содержимое ячейки памяти 000004 в ре-
гистр R0”, а не то, что нам нужно — ’’переслать число 4 в регистр R0”. Необ-
ходима какая-то новая нотация, и представляется вполне естественным ис-
пользовать знак номера (#) для обозначения того, что источником являет-
ся абсолютное число. Таким образом, записываем
MOV #4, R0
Есть еще два способа, которые мы применяли для использования регист-
ров общего назначения в некоторых инструкциях, и для них нам также необ-
ходимы нотации. Во-первых, в программе предыдущего раздела содержимое
R2 интерпретировалось как адрес числа, а не само число. Во-вторых, в ин-
струкции сравнения (в 000016), при обращении к R5 и R2 выполнялось срав-
нение несодержимого R5 с содержимым R2, а содержимого R5 с содержимым
ячейки памяти, адрес которой находился в R2. Таким образом, мы не можем
написать
CMP R5,R2
поскольку это означает ’’сравнить содержимое R5 с содержимым R2”. Мы
должны создать какую-то нотацию для этого нового (и, как мы видели, йо-
лезного) способа использования регистров. Поскольку мы применяли нота-
цию с(...), чтобы обозначить ’’содержимое чего-то”, давайте применим нота-
цию (R2) для индикации того, что мы ссылаемся на содержимое ячейки, ад-
рес которой находится в R2, а не на само содержимое R2. На основе этого со-
глашения, мы можем записать в мнемоническом виде инструкцию сравнения
предыдущей программы так:
CMP R5, (R2)
Есть все же еще один способ использования регистров, который мы при-
меняли. Например, в программе сложения на рис. 5.10.1. Содержимое R2
выступает как адрес числа, подлежащего сложению, однако вспомним, что
каждый раз при сложении содержимое R2 автоматически увеличивалось на
2. Поэтому для обозначения этого автоувеличения необходим какой-то вари-
ант только что придуманной нотации (R2). Подходящей оказывается нота-
ция (R2)+; таким образом,
ADD (R2)+,R5
показывает, что содержимое R2 увеличивается на 2 после использования его
в качестве адреса числа, подлежащего сложению с c(R5) .
92
6.4. ОБРАЗЕЦ ПРОГРАММЫ, ПЕРЕПИСАННЫЙ В МНЕМОНИЧЕСКОМ ВИДЕ
Теперь мы располагаем достаточным числом мнемоник и регистровых но-
таций, чтобы переписать программу разд. 6.2, используя на этот раз преиму-
щества придуманных нами аббревиатур инструкций. Вспоминаем, что основ-
ной нашей целью при создании этих мнемоник было дать возможность сосре-
доточить свое внимание на логическом течении выполнения программных
инструкций, не отвлекаясь постоянно на поиск адресов, определение кодов
инструкции и смещений. На этот раз мы увидим, что задача написания про-
граммы существенно облегчается и приносит большее удовлетворение. Чита-
тель может обращаться к рис. 6.2.2, где показан полный листинг этой про-
граммы. Как предпочитают многие программисты, мы включаем здесь неко-
торые комментарии, облегчающие понимание того, что происходит.
Инструкция Комментарии
MOV #4, R0 Переслать число 4 в R0 в качестве счетчика.
MOV #??????,R2 Переслать адрес первого числа в R2.
Начали мы неплохо. Но одна из проблем, встреченных прежде (невозмож-
ность предвидеть адрес первого числа, так называемая ссылка вперед), опять
нам мешает, а это как раз тот тип трудностей, которого мы надеялись избе-
жать, используя мнемонический язык для написания программ. Эту пробле-
му мы можем обойти, если придумаем символ, такой как DATA, для замены
этого адреса. Таким образом, DATA — это просто число, а именйо, неизвест-
ный адрес первого из пяти чисел, максимальное из которых должно быть най-
дено. Поэтому мы можем здесь вставить инструкцию
MOV #DATA,R2
Из разд. 6.2 нам известно, что в действительности DATA = 000032, так что
эта инструкция эквивалентна следующей:
MOV #000032, R2
Заметьте, между прочим, что если бы мы по невниманию написали
MOV DATA,R2
[отсутствует знак номера (#)], то это было бы эквивалентно
MOV 000032,R2
что в соответствии с нашими соглашениями о нотации пересылало бы содер-
жимое ячейки памяти 000032 (а именно, первое из пяти чисел) в R2. Но зто
вовсе не то, что мы хотели сделать.
Справившись с проблемой адресации или, по крайней мере, отсрочив ее,
мы можем теперь продолжать работу со следующей программой:
Инструкция
MOV #4,R0
MOV #DATA,R2
MOV (R2),R5
INC R2
INC R2
CMP R5,(R2)
Комментарий
Переслать число 4 в R0 в качестве счетчика.
Переслать адрес первого числа в R2.
Переслать первое число в R5 (временный мак-
симум) .
Увеличить c(R2) дважды (чтобы оно содержа-
ло адрес второго числа).
Сравнить ТЕМРМАХ со следующим числом.
93
BGE ?????? Если результат сравнения больше или равен О,
ветвление в ??????.
Здесь мы опять сталкиваемся со ссылкой вперед — нам неизвестен буду-
щий адрес (в этом случае — адрес инструкции, к которой мы хотим осуще-
ствить ветвление). Теперь мы знаем, что необходимо условное ветвление в
обход инструкции, заменяющей временный максимум в R5. Эту трудность
мы можем избежать тем же способом, каким обрабатывали адрес первого
числа, просто назвав его DATA. Поэтому назовем адрес, к которому должен
осуществляться переход, символьным именем NEXT. Это позволяет нам про-
должать программирование и отодвинуть фактическое построение оператора
BGE на более позднее время.
BGE NEXT Если результат сравнения больше или равен О,
ветвление к NEXT.
MOV (R2) ,R5 Заменить TEMPMAX текущим числом.
NEXT: DEC R0 Уменьшить счетчик.
Обратите внимание.на то, что мы здесь делаем. Нам понятно, что инструк-
ция DEC R0 располагается в той ячейке памяти, к которой отсылает инструк-
ция BGE в случае, когда не требуется замены временного максимума. По-
скольку в мнемонической форме инструкции BGE мы ссылаемся на эту ячей-
ку памяти как на NEXT, NEXT представляет собой адрес ячейки, содержа-
щей инструкцию уменьшения. Важно понимать, что NEXT — это просто дру-
гое название этого числа. Обратите также внимание на то, что символ NEXT
помещен слева от самой инструкции. Это совершенно естественно по следу-
ющей причине. Мы не записываем, как делали прежде, адреса ячеек памяти
различных инструкций по мере написания программы. Действительно, одной
из целей настоящего подхода к программированию на машинном языке бы-
ло избавить нас от этой и других нагрузок. Но если бы мы записывали эти ад-
реса, чтобы следить за тем, что уже было сделано, то, вероятно, поместили
бы их слева. Таким образом, символ NEXT оказывается в самой левой ко-
лонке именно потому, что это есть адрес, хотя в данный момент мы не толь-
ко не знаем, но и не заботимся о том, каково его числовое значение. И, на-
конец, обратите внимание, что после символьного имени NEXT мы помести-
ли двоеточие. Этот факт не является существенным; двоеточие прежде всего
помогает отличать символьные адреса, такие как HALT:, от мнемоники ин-
струкции HALT.
Следующее, что нужно сделать в программе, это осуществить ветвление
назад к первой из двух инструкций увеличения в случае, когда счетчик не ра-
вен 0. Этой инструкции увеличения мы присваиваем символьное имя LOOP
LOOP: INC R2
и затем вставляем инструкцию
BNE LOOP Ветвление назад, если счетчик не равен 0.
Теперь остались единственная инструкция HALT (если счетчик равен 0)
и данные. Размещая данные, мы не должны забывать о присвоении символь-
ного имени DATA адресу первого из пяти чисел. Теперь мы имеем закончен-
ный продукт, показанный на рис. 6.4.1.
94
Метка Инструкция Комментарий
MOV #4,R0 Переслать число 4 в R0 в качестве счетчика.
MOV #DATA, R2 Переслать адрес первого числа в R2.
MOV (R2),R5 Переслать первое число в R5 (временный максимум}.
LOOP: INC R2 Увеличить с (R21 дважды
INC R2 (чтобы оно содержало адрес следующего числа).
CMP R5, (R2) Сравнить временный максимум со следующим числом
BGE NEXT Если результат сравнения больше или равен 0,
MOV (R2), R5 ветвление к NEXT. Заменить временный максимум текущим числом.
NEXT.DEC R0 Уменьшить счетчик.
BNE LOOP Ветвление назад, если счетчик ненулевой.
HALT DATA: 403 102304 23704 7702 176337 Стоп, если счетчик равен 0. Пять чисел, максимальное из которых должно быть найдено, загружаются здесь.
Рис. 6.4.1
Заголовок самой левой колонки ’’.метка” согласуется с основной термино-
логией по ЭВМ PDP-11. Как мы знаем, эти метки являются просто символь-
ными именами адресов соответствующих им ячеек памяти. Помимо прочего,
обратите внимание на то, что этот листинг программных инструкций оказы-
вается удобно читаемым (сравните со списком машинных инструкций на
рис. 6.2.2). Теперь можно довольно легко просмотреть эту программу и с
удовлетворением отметить, что она логически правильна. Еще раз обратим
внимание на ту важную особенность, что, как только читатель познакомился
с мнемониками и другими нотациями, он способен записать эти инструкции,
концентрируя свое внимание только на логике программы без постоянного
отвлечения на поиск кодов, определение адресов и вычисление смещений.
Теперь мы покончили с творческими аспектами конструирования програм-
мы и настал час расплаты. То, что мы создали, не является, конечно, выпол-
няемой программой, поскольку это набор символов (мнемоник), а ЭВМ мо-
жет выполнять только свой собственный машинный код. Таким образом, мы
подошли к утомительному процессу перевода этих мнемоник в машинные
инструкции с сопутствующими вычислениями и другой нудной работой. Как
только это будет сделано, мы сможем загрузить машинный код и данные в
оперативную память и выполнить машинную программу. Прежде чем погру-
жаться в детали, зададимся вопросом: а что же в действительности должно
быть здесь сделано? Ответ, как мы знаем, состоит в том, что мы должны
отыскивать каждую мнемонику (например, в таблице) для определения со-
ответствующего ей машинного кода и способа использования этой инструк-
ции, определять адреса, определять символьные значения и вычислять разно-
образные смещения. На этой стадии никакого знания логики программы не
требуется. Фактически не нужно даже знать программирование вообще, а об-
ладать только способностью поиска мнемоник и внимательного выполнения
тривиальных вычислений. Кажется, что мы могли бы просто вручить програм-
му, записанную в мнемоническом виде, служащему, имеющему доступ к
95
таблицам мнемоник, и попросить его вернуть программу через некоторое
время с соответствующими машинными инструкциями. И это как раз то, что
мы сделаем.
6.5. АССЕМБЛИРОВАНИЕ МНЕМОНИЧЕСКОЙ ПРОГРАММЫ
В этом разделе мы познакомимся с некоторыми деталями задач, с кото-
рыми сталкивается наш служащий при ассемблировании мнемонической про-
граммы {ассемблирование — это термин, обычно используемый для обозна-
чения процесса преобразования мнемонических инструкций в машинный код).
Вспоминаем, что служащий располагает списком мнемоник, приведенных на
рис. 6.4.1, но нам нет нужды заботиться о включении комментариев, посколь-
ку для него они ничего не значат, — ведь он не знает, какова логика програм-
мы, для чего эта программа предназначена и даже что означают мнемоники.
Первым делом служащий задастся вопросом о том, где программа должна
ассемблироваться, т. е. каким окажется адрес первого слова программы, ко-
гда она в конце концов будет загружена. Поскольку никакой информации об
этом мы ему не даем, он по умолчанию использует значение 000000. (Вспо-
минаем, что знание этого начального адреса и, следовательно, всех последую-
щих адресов необходимо для построения некоторых машинных инструкций.)
Дальше служащий может рассуждать так. Мнемоническая программа, веро-
ятно, будет содержать символьные ссылки и, конечно, эти инструкции нельзя
будет ассемблировать до тех пор, пока не станут известными значения симво-
лов. Таким образом, он осуществит первый проход по программе, чтобы
определить эти значения. В необходимости поступать именно так можно убе-
диться, посмотрев первые несколько инструкций мнемонйческой программы
на рис. 6.4.1.
Как упоминалось ранее, первая инструкция, MOV #4,R0 будет загружать-
ся в ячейку 000000. Но куда попадет следующая инструкция, MOV #DATA,
R2? Чтобы это определить, служащий должен знать, сколько слов оператив-
ной памяти потребуется для MOV #4,R0. Он может посмотреть в свою табли-
цу и определить, что здесь используется тот тип операции пересылки, кото-
рый переносит абсолютное число в регистр и занимает два слова, хотя в этот
момент служащему не нужно заботиться о том, какими будут эти слова. Те-
перь он знает, что инструкция MOV # DATA,R2 будет загружена в ячейку
000004. Поскольку весьма вероятно, что служащий ведет счетчик адресов
(называемый счетчиком распределения ячеек), то он увеличит его до 000004
и приступит к изучению следующей инструкции, MOV #DATA,R2. Здесь
следует отметить два момента. Во-первых, инструкция опять имеет вид ’’пе-
реслать абсолютное число в регистр”, для чего, как мы знаем, требуются два
слова памяти. Поэтому, завершая изучение этой инструкции, служащий уве-
личит счетчик распределения ячеек на 4 (два слова эквивалентны четырем
байтам). Но перед тем, как перейти к следующей инструкции, он обнаружи-
вает наличие символьного имени DATA. В этом месте он не знает его значе-
ния, но поскольку это программный символ, служащий должен поместить
его в таблицу символов. Обычно в эту таблицу он должен также включать и
значение символа, но поскольку оно неизвестно, вход для этого символа в
96
таблице мог бы выглядеть примерно так:
DATA
Звездочки показывают, что значение символа неизвестно.
Теперь служащий видит инструкцию MOV (R2), R5 и, поскольку для нее
требуется одно слово, увеличивает счетчик распределения ячеек на 2. К мо-
менту, когда он встретит первую инструкцию увеличения, его рабочая тет-
радь будет выглядеть примерно так:
Адрес Содержимое Метка Мнемоника
000000 MOV #4,R0
000002
000004 MOV #DATA,R2
000006
000010 MOV (R2),R5
000012 LOOP: INC R2
(Колонка, помеченная словом содержимое, остается незаполненной, посколь-
ку, как нам известно, при первом проходе по мнемонической программе слу-
жащий интересуется только распределением адресов и значениями символов.)
Здесь он сталкивается с другим символом LOOP. И поскольку его нет в табли-
це символов, служащий делает соответствующий табличный вход. Однако об-
ратите внимание, что, хотя значение символа DATA ему не было известно, зна-
чение символа LOOP он знает (это 000012), и табличный вход выглядит так:
LOOP 000012
Аналогично служащий просматривает остальную часть программы, назна-
чая адреса в соответствии со своим счетчиком распределения ячеек, вставляя
символы в таблицу по мере того, как они встречаются и т. п. Обратите внима-
ние, как он обрабатывает инструкцию
DATA: 403
Он понимает, что встретился с определением символа ВАТА. Взглянув на свой
счетчик распределения ячеек, он видит, что DATA - это другое название ад-
реса 000032. Он видит также, что символ DATA уже присутствует в таблице
символов со значением ♦♦♦♦♦♦ (неизвестное значение). Поэтому теперь он
может стереть звездочки и заменить их значением 000032. В конце первого
прохода его рабочая тетрадь будет выглядеть следующим рбразом:
Адрес Содержимое Метка Мнемоника
000000 MOV #4,R0
000002
000004 MOV #DATA,R2
000006
№ 000010 MOV (R2),R5
000012 LOOP: INC R2
000014 INC R2
000016 CMP R5,(R2)
000020 BGE NEXT
000022 MOV (R2), R5
000024 next: DEC R0
4 Зак. 2212
97
000026 BNE LOOP
000030 HALT
000032 DATA: 403
000034 102304
000036 23704
000040 7702
000042 176337
В таблице символов появятся следующие входы (в том порядке, в каком
они встретились):
DATA 000032
LOOP 000012
NEXT 000024
Теперь служащий готов проделать фактическое ассемблирование, т. е. ге-
нерацию машинного кода для каждой инструкции, и, поскольку все символы
теперь определены, он не должен испытывать трудностей при заполнении ко-
лонки содержимое. Так как этот процесс мы подробно разобрали ранее, здесь
на нем останавливаться не будем. Затем служащий вернет нам заполненную
рабочую тетрадь, мы сможем загрузить машинный код в оперативную па-
мять, начиная с ячейки 000000, и выполнить машинную программу.
Прежде чем закончить этот раздел, стоит дать некоторые пояснения. Пред-
положим, что мы случайно забыли поместить метку NEXT на инструкцию
DEC R0. Когда служащий впервые встретит этот символ в инструкции BGE
NEXT, он должен поместить его в таблицу символов, отметив, что его значе-
ние в настоящее время неизвестно:
NEXT ******
К моменту завершения первого прохода таблица символов будет такой:
DATA 000032
LOOP 000012
NEXT ******
На втором проходе, во время которого генерируется фактический машин-
ный код, все будет идти хорошо до тех пор, пока служащий не попытается ас-
семблировать инструкцию BGE NEXT. Поскольку он должен вычислить по-
словное смещение, которое попадет в младший байт оператора BGE, и по-
скольку ему известен текущий адрес (значение счетчика распределения яче-
ек), для завершения инструкции ему необходимо знать только значение сим-
вола NEXT. Просматривая таблицу символов, он находит, что NEXT никогда
не был определен, и поэтому служащий не в состоянии ассемблировать эту
инструкцию. Чтобы дать нам знать об этой проблеме, он помечает эту ин-
струкцию в своей рабочей тетрада кодом ошибки U, что означает ’’ссылка на
неопределенный символ”.
U 000020 ?????? BGE NEXT
Чтобы дать другой пример возможных затруднений, предположим, что в
одной и той же мнемонической программе мы использовали одну метку, на-
пример ANSWER:, больше одного раза. Особенно легко это может случиться,
если программа имеет большую длину. Предположим, что мы использовали
98
зту метку в ячейках 000046 и 000162. Когда служащий встречает метку
ANSWER: впервые, он заглядывает в свою таблицу символов. Этот символ
либо отсутствует в таблице, тогда служащий делает для него табличный вход
со значением 000046, либо он есть в таблице со значением ****** (т. е. на
него была более ранняя ссылка), тогда он стирает звездочки и заменяет их
значением 000046. Но в ячейке 000162 он снова встречает метку ANSWER,
Заглянув в таблицу символов, он находит вход
ANSWER 000046
и теперь он не знает, какое из двух значений предназначается для этого сим-
вола. С такой ситуацией он справиться не в состоянии,'и лучшее, что он мо-
жет сделать, это как-то указать, что мы передали ему программу, содержа-
щую многократно определенный символ.
Теперь предположим, что мы поторопились отдать служащему программу,
забыв вставить инструкцию HALT (более внимательный просмотр конечно-
го продукта позволил бы избежать этого). Но можно ли ожидать, что служа-
щий обнаружит эту логическую ошибку? Конечно, нет. Он ничего не знает о
программировании; его работа заключается в распределении адресов, нахож-
дении инструкций и ведении таблицы символов. Не следует ожидать, чтобы
он анализировал логический ход программы. Он вернет нам свою рабочую
тетрадь, отлично выполнив свою работу, но без какой-либо индикации того,
что в программе что-то неверно. Обратите, между прочим, внимание, что из-
за отсутствия инструкции HALT в этом случае он назначит символу DATA
значение 000030. Мы не сможем обнаружить неблагополучие до тех пор, пока
программа не будет загружена и выполнена. Затем мы вынуждены будем
проверить логику программы, заметим отсутствие инструкции HALT, вста-
вим ее в мнемоническую программу и возвратим служащему для нового ас-
семблирования.
И, наконец, предположим, что мы сделали ошибку, написав BNZ LOOP
вместо BNE LOOP. Когда служащий встретит эту строку в программе, он за-
явит, что не может отыскать мнемонику BNZ в своей таблице мнемоник и,
следовательно, не может сгенерировать для нее какой-либо код.
Мы уже говорили, что как только служащий возвратит нам свою рабочую
тетрадь (без каких-либо флагов ошибок), мы можем загрузить код в опера-
тивную память, начиная с ячейки 000000. Обратите внимание, что мы не мо-
жем по своей прихоти загрузить его где-либо в другом месте, например, начи-
ная с ячейки 001000, без возможных неприятных последствий. Совершенно
правильно служащий поместил в ячейку 000006 адрес 000032 первого слова
данных. Если мы попытаемся загрузить программу, как она есть, начиная с
ячейки 001000, то пять слов данных окажутся в оперативной памяти, начиная
с ячейки 001032, а не с 000032. Но поскольку инструкция MOV #DATA, R2
поместит в R2 значение 000032, программа при выполнении будет работать с
пятью ячейками памяти, расположенными вне нашей программы. Конечно,
если мы действительно хотим загрузить программу с ячейки 001000, то дол-
жны изменить значение слова в ячейке 000006 с 000032 на 001032. Однако в
большой программе не так-то легко найти многочисленные слова, требующие
подобного исправления. Короче говоря, пытаясь переместить программу с
того места, для которого она предназначена, мы испытываем затруднения и
4*
99
порождаем определенные сложности, т. е. как раз то, чего мы пытались избе-
жать в этом и предыдущем разделах.
6.6. АССЕМБЛЕР ЭВМ PDP-11
Рассмотрим вкратце те задачи, которые требовалось решать служащему в
предыдущем разделе. Он должен был инициализировать счетчик распределе-
ния ячеек в (000000) и затем вести его, увеличивая на 2 или на 4 по мере об-
работки инструкций. Он должен был также находить мнемоники в таблице,
выполняя сравнения на совпадением распознавать некоторые нотации, такие
как # и (..). Он должен был распознавать характер использования симво-
лов, что представлялось довольно легкой задачей, так как используемый
символ появляется в инструкции вслед за мнемоникой оператора и бывает
либо абсолютным числом, либо обозначением регистра. Определение симво-
лов (с помощью таблиц) было также тривиальным делом. Мы помогли ему в
этом, поместив двоеточие после меток или символьных определений. Хотя
существует еще несколько дел, которым он должен был уделять внимание,
в этом процессе не видно ничего такого, что не могло быть сделано самой ЭВМ,
выполняющей соответствующую программу. Эта программа должна скани-
ровать строки мнемонической программы (так называемой исходной про-
граммы), просматривать таблицы (без сомнения, с помощью оператора СМР),
вычислять смещения (используя оператор вычитания SUB) и т. п. Ее выхо-
дом — объектной программой — конечно, должен быть машинный код для
исходной (мнемонической) программы. Такая транслирующая программа
называется ассемблером, и ее задачей будет в точности то, что делал наш
служащий. 7
Написать ассемблер — задача не тривиальная. Хотя легко можно сказать,
что ассемблирование — весьма простая работа для служащего, но совсем дру-
гое дело написать программу, которая сканирует строку мнемоник, распо-
знает различные компоненты (такие, как метки, операторы и символы ре-
гистров) , правильно ведет счетчик распределения ячеек и т. п. Но награда бы-
ла бы немалой. Во-первых, мы смогли бы отказаться от использования тру-
да служащего (а более вероятно, служащих, потому что один программист
способен занять нескольких). И во-вторых, ассемблирование стало бы во-
просом минут, а не часов или дней, поскольку вычислительные машины ра-
ботают быстрее, чем люди.
На рис. 6.6.1 показан вывод ассемблера ЭВМ PDP-11 для мнемонической
программы (или, как ее обычно называют, программы на языке ассемблера),
которую мы рассматривали в этой главе. Соглашения, используемые ассемб-
лером ЭВМ PDP-11, приведены в приложении Г. Обратите внимание, что ас-
семблер распечатывает каждую строку мнемоник, или исходный код. Это де-
лается специально для нашего удобства, чтобы мы располагали распечатанной
копией каждой строки инструкции и могли посмотреть, как ассемблер ее об-
рабатывает. Кроме того, видно, что ассемблер назначает каждой строке исход-
ного кода (десятичный) номер строки (числа в самой левой колонке). Это
также делается для удобства программиста. Следующая колонка содержит
адреса ячеек, в которые ассемблируются инструкции; затем следует содержи-
мое этих ячеек памяти. Обратите, однако, внимание, что если инструкция со-
стоит из больше, чем одного слова, то ассемблер распечатывает адрес только
100
1 000000 012700 000004 MOV ♦4» R0
2 000004 012702 MOV *DATA»R2
000032
3 000010 011205 MOV (R2>*R5
4 000012 005202 LOOPS INC R2
5 000014 005202 INC R2
6 000016 020512 CMP R5»<R2>
7 000020 002001 BGE NEXT
8 000022 011205 MOV <R2)>R5
9 000024 005300 NEXT l DEC R0
10 000026 001371 ONE LOO?
11 000030 000000 HALT
12 000032 000403 DATA: 403
13 000034 102304 102304
14 000036 023704 23704
15 000040 007702 7702
16 000042 176337 176337
17 .END
Рис. 6.6.1
первого из этих слов. Поэтому, например, в первой инструкции программы
(MOV #4, R0) мы не видим адреса 000002, по которому помещается число
000004. И, наконец, посмотрите на последнюю строку. END. Этот ’’оператор”
мы включили, чтобы показать ассемблеру, что он достиг конца исходной про-
граммы (для служащего мы этого не делали, поскольку полагали, что, про-
смотрев все мнемоники, он будет считать первый проход завершенным).
Оператор .END не является программной (машинной) инструкцией, и ас-
семблер не генерирует для него кода и даже не назначает адреса. В противо-
положность машинной инструкции .END — это такой оператор, который пред-
писывает ассемблеру сделать что-то, в частности остановить ассемблирование,
поскольку программа исчерпана. Такой тип оператора мы называем директи-
вой ассемблера; дальше в тексте мы познакомимся еще с несколькими по-
добными информационными операторами. Они легко распознаются, посколь-
ку начинаются с точки.
Мы говорили ранее, что выбор мнемоник — это дело программиста, кото-
рый может придумать любые миниспова, облегчающие ему запоминание ап-
паратных инструкций, к которым эти мнемоники относятся. Если позднее
программист решает, что какая-то другая мнемоника более подходит для ин-
струкции, чем первоначально выбранная, то изменение не составляет боль-
шой проблемы. Но теперь, когда ассемблирование выполняется программой
на машинном языке и эта программа содержит список (вероятно, в таблице)
допустимых мнемоник, изменение мнемоник потребует модификации самой
программы (ассемблера). Хотя это возможно, обычно так не поступают. Од-
нажды выбрав набор мнемоник и написав их распознающий ассемблер, мы
обычно придерживаемся их неопределенно долго. Мнемоники ассемблера
ЭВМ PDP-11 перечислены в приложении А.
6.7. МОДИФИКАЦИЯ ОБРАЗЦА ПРОГРАММЫ
Читатель, возможно, заметил, что одним из недостатков рассмотренных
до сих пор программ является то, что,выполнив необходимые вычисления,
программа просто предписывает процессору остановиться (HALT). Поэтому,
хотя ответ известен и находится в каком-то регистре или ячейке памяти, мы
не располагаем средством заставить программу как-то объявить результат.
В заключение главы мы приведем некоторую модификацию программы по-
101
MAXIMUM
1 .TITLE MAXIMUM
2 1
3 000000 012700 000004 START» MOV 94» R0 ;СРАВНИВАТЬ 4 РАЗА
4 000004 012702 000074’ MOV €DATA»R2 ;АДРЕС 1-ГО ЧИСЛА В R2
5 000010 011205 MOV (R2),R5 ;1-Е ЧИСЛО = ВРЕМ. МАКСИМУМ
6 000012 005202 LOOP: INC R2 ;УВЕЛИЧИТЬ АДРЕС В
7 000014 005202 INC R2 ; R2 НА 2
8 000016 020512 CMP R5»<R2) ;СРАВНИТЬ ВРЕМ. МАКС. С ЧИСЛОМ
9 000020 002001 BGE NEXT SBPEM. МАКС. ВОЛЬПЕ — ЯРОПУСТИТЬ
10 000022 011205 MOV (R2>»R5 ;ЗАМЕНИТЬ ВРЕМ.МАКС.
11 000024 005300 NEXT: DEC R0 JУМЕНЬШИТЬ СЧЕТЧИК НА 1
12 000026 001371 ВНЕ LOOP ;СЛЕДУВШЕЕ ЧИСЛО» ЕСЛИ НЕ НОЛЬ
13 000030 010567 000036 MOV R5,ANSWER ПОЛУЧИТЬ МАКСИМУМ
14 000034 «OUT.OCT tANSWER.*1 ; И РАСПЕЧАТАТЬ ЕГО
15 000070 «EXIT »ВЕРНУТЬСЯ К ОЯЕРАЦ. СИСТЕМЕ
16
17 000072 ANSWER» .BLKW 1 ;ДЛЯ РАСПЕЧАТКИ МАКСИМУМА
18 ;ДОЛЖЕН BUTb НАЙДЕН
19 ; МАКСИМУМ СРЕДИ СЛЕДУЮЩИХ 5 ЧИСЕЛ
20
21 000074 000403 DATA: .WORD 403
22 000076 102304 .WORD 102304
23 000100 023704 WORD 23704
24 000102 007702 .WORD 7702
25 000104 176337 .WORD 176337
26
27 000000’ .END START
MAXIMUM
ТАБЛИЦА СИМВОЛОВ
ANSWER 000072R LOOP 000012R START OOOOOOR
DATA 000074R NEXT 000024R *.... = «««««« 6
Рис. 6.7.1
В распечатках программ вместо знака $ используется знак Q.
иска максимума, которая действительно распечатывает максимальное из пя-
ти чисел, хотя, поступая так, мы, вероятно, породим больше вопросов, чем
дадим ответов. Мы также уделим большее внимание листингам, порождае-
мым ассемблером, поскольку на практике они более сложны, чем простой
листинг, показанный на рис. 6.6.1.
Обращаясь к рис. 6.7.1, мы замечаем, что во многие строки исходного ко-
да могут быть включены комментарии. Ассемблер относится к этому терпе-
ливо, если только комментариям предшествуют точка с запятой. Фактически,
когда ассемблер обнаруживает точку с запятой, он полагает, что дошел до
конца строки исходного кода. С комментариями ассемблер ничего не делает,
но сохраняет их для последующей распечатки в листинге программы. Это
еще один пример того, что ассемблер делает исключительно для удобства
программиста, — ведь очевидно, что комментарии несущественны для генера-
ции машинного кода. Как видно, например, из строк 2 и 19, комментариями
могут быть целые строки, если первым знаком на строке оказывается точка
с запятой. Хотя комментарии и несущественны, они значительно увеличивают
степень читаемости мнемонической программы и облегчают прослеживание
развития логики программы.
Посмотрим теперь на строку 1: .TITLE MAXIMUM. Это еще один пример
директивы ассемблера, которая предписывает ассемблеру сделать для нас
что-то отличное от трансляции мнемоник, а именно, присвоить титул, или на-
звание, исходной программе. Опять эта особенность не влияет на существо
102
дела — программы не обязаны быть титулованными. Однако, если программа
титулована, то слово или слова титула будут появляться в заголовке листин-
га, в верхней части каждой его страницы. (Хотя в заголовке листинга мы по-
казали только титул, в действительности в этом заголовке распечатывается
и другая полезная информация, такая как дата и время распечатки листин-
га. В большинстве своем эта информация понятна сама по себе.) Если ди-
рективы .TITLE в программе нет, то ассемблер по умолчанию использует ти-
тул .MAIN.
Операторе строке 14:
$OUT.OCT #ANSWER,#1
требует весьма детальных пояснений, большую часть из которых сейчас дать
невозможно. Тем не менее, мы можем сделать по его поводу несколько пред-
положений в учебных целях. Из комментариев на этой строке очевидно,что
мы здесь распечатываем максимальное число. Поэтому мы можем заключить,
что SOUT.OCT обозначает ’’вывести в восьмеричном формате”1, что подле-
жащее выводу слово находится в ячейке памяти с символьным адресом
ANSWER и что должно быть распечатано одно (#1) число. Все эти выводы
правильные, и если программа выполняется, мы видим такую распечатку:
023704
(Напоминаем, что 102304 и 176337 — отрицательные числа.)
Несмотря на то, что SOUT.OCT #ANSWER,#1 выглядит наподобие мне-
моники ассемблера ЭВМ PDP-11 для какой-либо машинной инструкции, из
набора инструкций (см. приложение А) видно, что это не так. Это также и не
директива ассемблера, которая обычно начинается с точки. В равной степени
удивителен тот факт, что, чем бы на самом деле SOUT.OCT ни было,оно за-
нимает немалый участок памяти — от 000034 до 000066, что составляет 348
байт, или 168 = 1410 слов. Самое большее, что можно здесь сказать, это то,
что SOUT.OCT — макроинструкция, состоящая из ряда обычных машинных
инструкций ЭВМ PDP-11, которые вместе с их машинным кодом в листинге
не показаны, хотя ассемблер поместил нужный код в эти ячейки памяти. Де-
тали использования этой и других команд ввода-вывода для выполнения пе-
ресылки данных, можно найти в приложении Б.
В строке 15 содержится другая макроинструкция, SEXIT, хотя видно, что
она занимает только одно слово (два байта) оперативной памяти. Опять код
этой инструкции не показан в листинге, но ассемблер поместил его в ячейку
000070. Здесь мы можем сказать лишь, что SEXIT должна включаться в лю-
бую программу, когда мы хотим прекратить ее выполнение (хотя сейчас мы
находимся в предварительной фазе ассемблирования, мы заглядываем впе-
ред в то время, когда программа будет фактически загружаться в оператив-
ную память и выполняться). В последних примерах, после того как полно-
стью выполнена программа, мы просто инструктировали процессор о том,
что он должен остановиться (HALT). Макроинструкция SEXIT занимает ме-
сто ранее используемой инструкции HALT и обеспечивает организованное
окончание выполнения программы и возврат управления процессором опе-
рационной системе — среде, в которой выполняется наша программа.
1 По-английски ’’выводить” - OUTput, ’’восьмеричный” - OCTal - Прим, перев.
103
В строке 17 содержится .BLKW (Block of Words — блок слов). Это опять
директива ассемблера, которая требует некоторых пояснений. Вспомним,
что когда ассемблер сканирует исходную программу, он ведет счетчик рас-
пределения ячеек. При обработке каждой инструкции он увеличивает этот
счетчик на 2 или больше, но всегда на четное число, так что его значением бу-
дет адрес ячейки памяти для следующей инструкции. Директива .BLKW п
предписывает ассемблеру увеличить в этом месте счетчик распределения яче-
ек на 2п, что создает в объектной программе промежуток в 2п байт (п слов).
Можно предположить, что программисту необходимы несколько слов памя-
ти в программе, но ему безразлично, что содержится в этих словах, а в про-
цессе выполнения программы эти слова будут заполнены осмысленными дан-
ными. Здесь именно тот случай. Мы предписываем ассемблеру оставить одно
слово (два байта) памяти в ячейке 000072, чтобы при выполнении програм-
мы, когда будет найден максимум, он мОг быть помещен в эту ячейку (стро-
ка 13: MOV R5, ANSWER) и распечатан.
Другой директивой ассемблера, аналогичной директиве .BLKW, является
.WORD, которую мы видим в строках 21 и 25. Здесь, однако, вместо указа-
ния ассемблеру, чтобы он перескочил через слово оперативной памяти, мы
требуем, чтобы ассемблер поместил в это слово заданное значение. Таким об-
разом, .WORD 403 в ячейке 000074 предписывает ассемблеру вставить слово
000403 в ячейку памяти 000074, что, как мы видим, он и сделал. Читателя
может несколько смутить тот факт, что в листинге на рис. 6.6.1 (см. строки с
12 по 16) директива .WORD отсутствует, а нужные числа просто вставлены в
исходный код в этом месте. Ассемблер все-таки обрабатывает эту ситуацию
правильно, потому что, когда он встречает строку исходного кода, содержа-
щую (помимо возможной метки) исключительно число или числа, то по
умолчанию он подразумевает директиву .WORD. Поэтому использование
этой директивы в подобных обстоятельствах необязательно. В остальной ча-
сти текста мы будем использовать директиву .WORD, когда это будет пред-
ставляться полезным, в противном случае будем давать возможность ассемб-
леру поступать по умолчанию.
Если исходной программой должно быть порождено больше одного сло-
ва, то можно поместить последовательность чисел на одной и той же строке
исходного кода, разделяя их запятыми. Таким образом, строки с 21 по 25
нашей программы могут быть записаны (и более легко) так:
DATA: .WORD 403,102304,23704,7702,176337
или даже так:
DATA: 403,102304,23704,7702,176337
Любая из этих записей приведет к размещению в ячейках от 000074 до 000104
указанных чисел в заданном порядке.
Обратите внимание на то, что в оператор .END на строке 27 включен адрес
START. Он определяет место, с которого должно начинаться выполнение про-
граммы. Поскольку задачей ассемблера является просто трансляция мнемо-
ник и построение машинного кода, но, конечно, не выполнение программы,
стартовый адрес для него значения не имеет. Этот стартовый адрес зачастую
называют адресом передачи управления, т. е. таким адресом, на который дол-
жно быть передано управление при начале выполнения программы. Тем не
104
менее ассемблер обращает внимание на этот стартовый адрес для последую-
щих ссылок. Как и что осуществляет ссылки на стартовый адрес, станет по-
нятно несколько позднее. В большинстве программ оператор END содержит
стартовый адрес, но есть и исключения.
Требует внимания еще один вопрос. Ассемблер поместил число 000074 —
второе слово инструкции MOV #DATA,R2 в ячейку 000006. Это соответ-
ствует ссылке в инструкции на число DATA, и мы действительно видим, что
DATA имеет значение 000074. Однако это значение было помечено апостро-
фом (000074’). Этот флаг относится к тому, на что мы намекали раньше: по-
требуется исправление этого конкретного числа (000074 в ячейке 000006),
если объектная программа будет перемещаться, т. е. если программа будет
загружаться с некоторого адреса, отличного от 000000 (см. с. 99). Ассемб-
лер способен обнаружить, какие слова не могут оставаться правильными при
перемещении программы, и отмечает это в листинге апострофом, опять ока-
зывая помощь программисту.
И, наконец, можно сказать, что таблица символов является довольно по-
нятной. Вспомним, что ассемблеру нужно было построить эту таблицу на пер-
вом проходе, чтобы знать различные символьные адреса и вычислить необ-
ходимые смещения. Чтобы мы могли к ней обращаться, ассемблер распеча-
тывает таблицу. Обратите внимание, что символы размещаются по колонкам
в алфавитном порядке. Каждый определенный пользователем символ распе-
чатывается вместе с присвоенным ему ассемблером значением. Изучая лис-
тинг, читатель может убедиться в правильности символьных значений. Буква
R, следующая за символьными значениями, обозначает, что они перемещав2
мые (Relocatable), т. е. значения таких символов чувствительны к перемеще-
нию программы. Если программа перемещается в оперативной памяти, то
значения этих символов будут меняться. Следовательно, некоторые ссылки 1
на них должны быть исправлены, как это было с обращением к адресу DATA
в ячейке 000006.
Есть еще одна неясность. ’’Имя” $ . . . . появляется в таблице символов,
хотя и в несколько другом формате:
$... = ******g
Взглянув на листинг программы, мы видим, что это не есть символ .определен-
ный пользователем. Как следует из нашего прежнего обсуждения символов и
их значений, звездочки фактически показывают, что символ $. . .. никогда не
был определен в программе. Все это верно, и все же кажется, что ассемблер
нисколько не возмущен, встретив неопределенный символ, потому что в дей-
ствительности этот символ используется внутри макроинструкции $OUT.ОСТ.
Приведя довольно полный листинг программы, порожденный ассемблером,
мы, по-видимому, внесли ряд сложностей и не со всеми из них справились.
Это неизбежно, хотя в какой-то степени может возмутить читателя. Все тай-
ны в существенной степени будут открыты. И хотя мы обычно избегаем вы-
сказываний типа ’’пока не беспокойтесь об этих деталях”, это, вероятно, на-
илучший совет, который можно дать в настоящее время.
6.8. УПРАЖНЕНИЯ
6.2.1. Как в начале разд. 6.2, запишите (на русском языке) алгоритмы для выполне-
ния следующих задач (читатель, знакомый с языком программирования высокого уров-
. 105
ня, например с Бейсиком или Паскалем, может завершить процесс и записать алгоритм
в виде программы) :
а) Найти сумму положительных целых чисел от 1 до 10 включительно.
б) Найти сумму нечетных положительных целых чисел от 1 до 9 включительно.
в) Разместить набор из семи чисел в порядке возрастания.
г) Привести набор из 12 положительных целых чисел и найти первое нечетно г число.
Если такового не существует, сообщить, что все числа четные.
6.2.2. Объясните, как проблема ссылки вперед, встретившаяся при попытке пере-
слать адрес первого из пяти чисел, максимум из которых нужно было найти, в регистр
R2 (см. с. 84), могла бы быть устранена, если бы мы решили загрузить эти пять чисел
перед инструкциями программы, а не после них. Была еще одна ссылка вперед, встре-
тившаяся при построении этой программы, а именно, в инструкции ветвления в ячейке
000020 (см. с. 86). Можно ли убрать эту ссылку, изменив или переупорядочив шаги
программы?
6.2.3. На с. 89 утверждалось, что поскольку старший байт инструкции ветвление-ес-
ли-больше-или-равно-нулю есть 004 и смещение (в младшем байте) равно 001, то кодом
инструкции становится 002001. Почему он не должен быть 004001?
6.2.4. Как следует модифицировать программу из разд. 6.2, если должно быть найде-
но минимальное из семи чисел?
6.2.5. Обычно при выполнении программы содержимое регистров общего назначения
(от R0 до R5) неизвестно до тех пор, пока программа не установит какие-то определен-
ные значения в одном или нескольких из этих регистров (например, с помощью такой
инструкции, как MOV # 4, R0). Напишите программу на машинном языке для повтор-
ного увеличения содержимого регистра 2 до тех пор, пока оно не примет значение 0;
после этого программа должна остановиться (обратите внимание на то, что первоначаль-
ное значение R2 неизвестно). Будет ли программа работать бесконечно, если первона-
чальное значение R2 положительно? Если да или нет, то почему? Что будет делать про-
грамма, если первоначальное значение R2 случайно равно 0?
6.3.1. В разд 6.3 мы приняли соглашение, что разумным сокращением для инструк-
ции ’’Переслать содержимое регистра 1 в регистр 4” может быть
MOV R1,R4
Рассмотрим следующее сокращение: *
MOV 1,4
Если принимается эта нотация, то как она повлияет на некоторые другие нотации, кото-
рые мы обсуждали и с которыми согласились в этом разделе?
6.3.2. Придумайте мнемоники для приведенных ниже инструкций. Затем сравните
их с мнемониками ЭВМ PDP-11, использованными для каждой инструкции. (Мнемони-
ки ЭВМ PDP-11 можно найти в приложении А. Прочитайте также описание каждой из
этих инструкций ЭВМ PDP-11.)
а) Ветвление, если бит переполнения в PSW установлен.
б) Ветвление, если бит переноса в PSW сброшен.
в) Безусловное ветвление.
г) Замена слова его дополнением до единицы.
д) Замена слова его дополнением до двух.
е) Сдвиг всех 16 бит слова на один бит влево.
ж) Сдвиг всех 16 бит слова на один бит вправо.
з) Перемена местами младшего и старшего байтов слова.
и) Нахождение ИСКЛЮЧАЮЩЕГО ИЛИ двух слов.
6.3.3. В соответствии с мнемониками из разд. 6.3 каждая из следующих инструкций
имеет такой смысл:
MOV 206, R0 Переслать содержимое ячейки памяти с ад-
ресом 206 в R0.
106
MOV R0, 206 Переслать содержимое R0 в ячейку памяти
с адресом 206.
MOV #206, R0 Переслать число 206 в R0.
Каким будет (если он есть) смысл
MOV R0, #206
Каким будет смысл таких инструкций, как
MOV #206, #614
CLR #4
INC #622
6.4.1. В мнемонической программе разд. 6.4 мы видели комбинацию
BGE NEXT
NEXT: DEC R0
Как читатель может заметить, это напоминает операторы языка Бейсик
100 IF X >= 0 THEN GO ТО 200
200 К = К - 1
или языка Фортран
IF (X.GE.0) GOTO 960
960 KOUTN = KOUNT - 1.
Как символьный адрес NEXT соотносится с номером строки 200 в Бейсике и иденти-
фикатором оператора 960 в Фортране?
6.5.1. В конце разде. 6.5 мы заметили, что если мнемоническая программа ассембли-
рована, начиная с адреса 000000, и мы решаем загрузить программу, начиная с адреса
001000, то слово в ячейке 000006 требует исправления. Какие другие слова этой про-
граммы (если таковые имеются) также должны быть модифицированы? (Обратитесь
к листингам мнемонической программы на рис. 6.4.1 и программы на машинном языке
на рис. 6.2.2.)
6.7.1. В разд. 6.7 мы утверждали, что SOUT.OCT является макроинструкцией. Най-
дите в словаре определение приставки макро и убедитесь, что его использование в слове
макроинструкция согласуется с тем, что мы говорили о SOUT.OCT.
6.7.2. В разд. 6.7 мы отмечали, что ассемблер помечает апострофом слова в объект-
ной программе, чувствительные к перемещению программы. Как ассемблер может опре-
делить, какие слова имеют отношение к этой проблеме?
ГЛАВА 7. ФОРМАТЫ ОПЕРАТОРОВ И РЕЖИМЫ АДРЕСАЦИИ
7.1. ОПЕРАТОРЫ И ИНСТРУКЦИИ
В гл. 5 мы неформально отмечали различие между оператором1, определя-
ющим одно из действий, на которые способен ЦП, и инструкцией — операто-
1 Здесь имеется в виду узкий смысл термина ’’оператор”, означающего собственно
подлежащую выполнению операцию, в противоположность его применению в более ши-
роком смысле - как ’’предложение” в языке программирования, задающее как опера-
цию, так и операнды, над которыми она должна выполняться. В последующем изложе-
нии термины ’’операция”, ’’оператор”, ’’инструкция” используются в обоих этих смыс-
лах, но всякий раз из контекста понятно, какой из них имеется в виду. - Прим, персе.
107
ром вместе с регистрами или ячейками памяти (или и тем и другим), с кото-
рыми он оперирует. В этой главе мы расширим и уточним эти понятия.
До сих пор, и особенно в предыдущей главе, мы говорили по мере необхо-
димости и об операторах, и об инструкциях и, хотя поясняли, что именно они
делают, тем не менее наш подход к изложению был в какой-то степени неор-
ганизованным. Например, в каких-то случаях 16-битовое слово, на которое
воздействовал оператор, было регистром, а в других - ячейкой памяти, ад-
рес которой находился в регистре. Аналогично иногда оператор автомати-
чески увеличивал содержимое регистра, а иногда нет. Мы встречали такой
оператор MOV, у которого пересылаемое слово было абсолютным числом
(MOV # 4, R0), содержимым регистра (MOV Rl, R4) или содержимым ячей-
ки памяти, адрес которой находился в регистре [MOV (R2), R5]. Если у чи-
тателя создалось впечатление, что существует много разных способов исполь-
зования одного оператора, то оно правильное. В то же время ситуация не яв-
ляется столь беспорядочной, как мы ее здесь представили. Действительно,
мы увидим, что она не только далека от хаоса, но здесь есть достаточно хоро-
шая структуризация и простота.
7.2. ТИПЫ ИНСТРУКЦИЙ И ФОРМАТЫ ОПЕРАТОРОВ
В нескольких образцах программ, рассмотренных до сих пор, мы видели,
что для некоторых инструкций требуются два слова памяти, например
012700 MOV #4, R0
000004
Для других же нужно только одно слово:
011205 MOV (R2), R5
или
000000 HALT
Для инструкции сложения в программе на рис. 5.8.1 требовалось три слова,
включая два адреса, и это максимальное число слов, которое может быть в
одной инструкции. Во всех случаях первое из (одного, двух или трех) слов
определяет саму операцию (т. е. действие, которое должен выполнять ЦП), а
любые дополнительные слова в инструкции определяют дальнейшую необ-
ходимую информацию, такую как абсолютное число или адрес.
Операционная часть инструкции, т. е. первое (или единственное) ее слово,
может быть представлена целым рядом различных форматов. Во-первых,
каждое операционное слово содержит некоторое число битов, определяющих,
какой будет операция ЦП. Небольшой обзор показывает, что, хотя, напри-
мер, мы используем операцию MOV различными способами, каждый из опе-
раторов, выполняющих пересылку,имеет следующий вид:
01XYXX
или в двоичном представлении:
0 001 ххх ххх ххх ххх
Именно лидирующие четыре бита, 0001, информируют ЦП о том, что долж-
на выполняться пересылка. Аналогично, оператор увеличения INC имеет вид
005 2АХ
108
или
О ООО 101 ОЮххх ххх
В этом случае операцию INC определяют 10 бит, 0000101010. И, наконец, мы
видели оператор, HALT, в котором операцию определяют все 16 бит: 000000 =
= 0 000 000 000 000 000. Лучшее, что можно сказать об этой ситуации, это то,
что действие ЦП определяют минимум четыре бита, максимум 16 бит, но все-
гда лидирующие (старшие) биты операционного слова. Независимо от того,
сколько их, биты, определяющие операцию, которую должен выполнять про-
цессор, называются битами операционного кода.
Если для битов операционного кода используются не все биты операцион-
ного слова, то какая информация передается в остальных битах? Нам бы хо-
телось дать понятный ответ на этот вопрос, но, к несчастью, использование
битов, не предназначенных для операционного кода, в существенной степени
зависит от самой операции. Вообще-то за небольшими исключениями осталь-
ные биты используются для указания какого-то регистра или некоторого
смещения или их комбинации, или же они не имеют значения для операции,
т. е. остаются неиспользованными. Все это, кажется, опровергает наше утвер-
ждение в предыдущем разделе, что операции весьма просты и хорошо струк-
турированы. Если читатель на некоторое время наберется терпения, то уви-
дит, что для подавляющего большинства операций это в самом деле так;
исключения же не приносят нам больших неприятностей.
7.3. УСЛОВНЫЕ ВЕТВЛЕНИЯ
Помимо нескольких операций, в которых для определения оператора ис-
пользуются все 16 бит (HALT, NOP, CLC и т. п.), наипростейшими в струк-
турном отношении являются операции условных ветвлений и безусловного
ветвления, BR. Мы здесь не будем много говорить о них (за исключением
способа изображения), поскольку в гл. 5 уже рассмотрели оператор ветвле-
ние-если-не-ноль, где отмечали, что все эти операторы ветвления формируют-
ся одинаково.
Вспомним, что оператор ветвления имеет вид (в двоичном представлении)
о ООО 000 о хх ххх ххх
где буква о представляет операционный код (в случае BNE — 0 000 001 0), а
буква х — пословное смещение со знаком. Поэтому, если условное ветвление
должно выполняться, то восьмибитовое смещение со знаком удваивается и
прибавляется к текущему содержимому PC.
7.4. ОДНО- И ДВУХОПЕРАНДНЫЕ ИНСТРУКЦИИ
Операнд, ассоциированный с операцией, — это некоторое слово или байт,
с которым операция работает. Это может быть, например, регистр или ячейка
памяти. Некоторые операции работают с одним операндом; три примера та-
ких операций: CLR R2, DEC R0, NEG R2. Для других, таких как MOV Rl, R4
или ADD (R2)+, R5, необходимы два операнда.
Здесь может оказаться полезной определенная терминология, и мы вве-
дем ее на примере известной нам инструкции MOV. Когда мы пишем
MOV R1,R4
109
то пересылаем число из регистра R1 в регистр R4. По этой причине регистр
R1 называется источником операции MOV, a R4 называется приемником. Ес-
ли обозначить через SS шесть битов, образующих источник, а через DD шесть
битов, составляющих приемник, то операцию MOV можно записать так:
О 1 5 5 D D
или в двоичном представлении, несколько расширяя запись, так:
О 001 sss sss ddd ddd
Если операция имеет только один операнд (такая, как CLR), то мы обычно
говорим о приемнике информации и записываем, например, код CLR таким
образом:
0 0 5 0 D D
или
0 000 101 000 ddd ddd
7.5. РЕЖИМЫ АДРЕСАЦИИ
Чтобы подойти к основной теме этого раздела, используем в качестве при-
мера известную нам операцию MOV. Мы уже отмечали, что в инструкции
MOV Rl, R4 регистр R1 представляет источник, a R4 — приемник этой опера-
ции. Код этой инструкции есть 010104, и его нетрудно записать в виде 16-би-
тового слова:
0 001 000 001 000 100
MOV Rl = R4 =
операционный код источник приемник
Почему, однако, для спецификации каждого из регистров источника и прием-
ника отведено по шесть битов, тогда как любой регистр от R0 до R7 можно
задать тремя битами от 000 до 111? Это происходит потому, что указания, ка-
кой регистр должен использоваться, недостаточно для определения источни-
ка или приемника, или обоих. Чтобы понять, почему это так, вернемся к ос-
новному примеру гл. 6 — программе для нахождения наибольшего из пяти
чисел.
Вспомним, что необходимо было переместить в R5 число, адрес которого
находился в R2, сначала для того, чтобы придать R5 первоначальное значение,
а позднее для замены значения R5 новым, когда встречается большее число.
Мнемоника для такой инструкции есть MOV (R2), R5, а восьмеричный код
равен 011205. Однако обратите внимание, что мы не пересылаем содержимое
R2 в R5, а используем содержимое R2 как адрес ячейки памяти, содержимое
которой должно пересылаться в R5. Действительно, из всего ранее сказанно-
го мы вправе (совершенно справедливо) ожидать, что код инструкции MOV
R2, R5 должен быть таким:
0 001 000 010 000 101
Однако мы только что утверждали, что код инструкции MOV (R2), R5 имеет
вид
0 001 001 010 000 101
' V ' -----у---' ----V---'
MOV (R2) = R5 =
операционный код источник приемник
110
Очевидно, что три бита в источнике (001) определяют способ, которым
R2 используется как источник, а именно, источником будет не содержимое
R2, а содержимое ячейки памяти,адрес которой находится в R2. Таким обра-
зом, из шести битов, специфицирующих регистр источника или приемника,
первые три бита определяют способ использования регистра — режим адреса-
ции регистра. Поэтому мы и будем теперь рассматривать биты источника или
приемника операнда следующим образом:
m m m t г г г ;
биты режима биты регистра
Очевидно, что биты режима могут задавать восемь возможных регистро-
вых режимов (от 0 до 7, т. е. от ООО до 111), и все они в действительности ис-
пользуются. Биты регистра могут определять восемь возможных регистров
от R0 до R7. Читатель должен помнить, что регистры от R0 до R5 мы называ-
ли регистрами общего назначения, R7 (PC) занимает некоторое особое поло-
жение как программный счетчик, но мы ничего не говорили об R6. При об-
суждении режимов адресации в оставшейся части этого раздела мы будем
включать и R6 как разрешенный регистр, упоминая порой об одной из его
специфических особенностей, но программный счетчик R7 мы исключим из
рассмотрения. Мы делаем это не потому, что наши дальнейшие высказывания
неприменимы к R7, а потому, что R7 обладает одной особенностью, которая
выделяет его среди остальных регистров — ЦП увеличивает его содержимое
(на 2) всякий раз после выполнения извлечения. Последствия применения
режимов адресации к PC будут рассмотрены в следующем разделе.
В этом и последующих разделах будет полезна некоторая дополнитель-
ная терминология. Эффективная ячейка (ЭЯ) , иногда в литературе по ЭВМ
PDP-11 называемая эффективным адресом (ЭА), — это восьмибитовый байт
или 16-битовое слово, используемое как источник или приемник операции.
Таким образом, например, в инструкции CLR R1 эффективной ячейкой яв-
ляется R1, тогда как в DEC (R3) — это 16-битовое слово, адрес которого на-
ходится в R3. Аналогично, для MOV (R2), R5 ЭЯ источника — это слово, ад-
рес которого находится в R2, хотя ЭЯ приемника — это R5.
1. Режим 0 (000)
Название: Регистровый (или непосредственный регистровый) режим
Символ: Rn
В режиме 0 эффективной ячейкой является сам указанный регистр. Это
самый простой из всех режимов адресации. Таким образом, например, опе-
рация, уменьшающая содержимое регистра R3, DEC R3, кодируется так:
0 000 101 011
\.______*_____
операционный код
уменьшения
= 005303
реж. 0 per. 3
приемник
А операция, которая пересылает (MOV) содержимое R5 в R2, кодируется
следующим образом:
0 00 1
»____v___/
операционный
код пересылки
^000^ 1 0 1 000 0 1 0 =010502
реж. 0 per. 5 реж. 0 per. 2
источник
приемник
Ш
2. Режим 1 (001)
Название: Косвенный регистровый режим
Символ: (Rn)
В режиме 1 эффективной ячейкой является восьмибитовый байт или 16-би-
товое слово, адрес которого находится в указанном регистре. Поэтому, если,
например, R3 содержит число 014622 и упоминается в режиме 1, то восьми-
битовым байтом или 16-битовым словом, являющимся источником или при-
емником операции, будет восьмибитовый байт или 16-битовое слово, адрес
которого есть 014622. Здесь важно отметить, что сам регистр не является
ЭЯ — регистр удерживает адрес ЭЯ. В качестве примера можно взять инструк-
цию в основной программе гл. 6 (см. рис. 6.6.1, строка 3).
Предположим, что регистр R2 содержит число 000032, т. е. c(R2) =000032,
а ячейка памяти с адресом 000032 содержит 000403, т. е. с(000032) =000403.
Тогда инструкция MOV (R2), R5 будет иметь такой машинный код:
0 0 0 1 001 010 000, 101 =011205
операционный реж: 1 per. 2 реж. 0 per. 5
код пересылки у *
источник приемник
После выполнения этой инструкции мы будем иметь c(R2) = 000032,
с(000032) = 000403 и c(R5) = 000403.
3. Режим 2 (010)
Название: Режим автоувеличения
Символ: (Rn)+
В режиме 2 ЭЯ такая же, как и в режиме 1, т. е. указанный регистр содер-
жит адрес ЭЯ. Единственное различие между режимами 1 и 2 состоит в том,
что сразу же после доступа к регистру для получения адреса ЭЯ ЦП автомати-
чески увеличивает содержимое указанного регистра. Выполняется автомати-
ческое увеличение на 1, если операция является байтовой инструкцией (та-
кой, как INCB), и на 2, если операция — пословная инструкция (такая, как
MOV или CLR). В случае байтовых операций есть два исключения: R6 и R7
(PC). Даже в байтовых инструкциях эти регистры всегда увеличиваются на 2,
а не на I1. В случае R7 вполне понятно, почему всегда должно выполняться
увеличение на 2. Как мы помним, PC всегда указывает на следующее слово,
подлежащее обработке, и, следовательно, должен содержать четный адрес
(адрес слова). Мы еще поговорим об этом в следующем разделе. То, что R6
представляет собой особый случай в этом отношении, не станет очевидным,
пока мы не познакомимся в следующей главе с желательностью такого осо-'
бого поведения.
1 Некоторые процессоры семейства ЭВМ PDP-11 выполняют автоувеличение и авто-
уменьшение (режим 4) в случаях R6 и R7 не совсем так, как здесь описано. Обсуждение
архитектурных различий, которые приводят к подобным противоречиям, остается за
пределами нашей книги. Однако мы увидим, что с этими различиями процессор справ-
ляется так, что они не оказывают существенного влияния на образцы программ, тексто-
вый материал или программирование, которым читатель может заниматься.
112
Чтобы проиллюстрировать режим 2, вернемся к последнему примеру (для
режима1) и вспомним, что с (R2) =000032, с(000032) = 000403. Рассмотрим
инструкцию MOV (R2)+, R5, код которой есть
0 001 010 010 000. 101 =012205
Ч-- --Z --< \-„--/ »--„-'
операционный реж. 2 per. 2 реж. 0 per. 51
код пересылки ’ * ' * '
г источник приемник
После выполнения инструкции мы имеем c(R2) = 000034 вследствие авто-
увеличения, с(000032) = 000403 и c(R5) = 000403. Если бы инструкция была
не пословная, а имела вид ’’переслать байт” (112205), то единственным отли-
чием было бы то, что после выполнения инструкции R2 содержал бы число
000033 из-за байтового типа инструкции, a R5 содержал бы 000003. (Почему?)
Чтобы показать, когда именно в процессе выполнения инструкции с авто-
увеличением оно происходит, рассмотрим следующую (по общему призна-
нию, странную) инструкцию: MOV (R4)+, (R4)+. Ее машинный код —012424.
Предположим, что перед выполнением этой инструкции c(R4) = 002642 и
с(002642) = 147306. Когда ЦП обращается к R4, чтобы получить ЭЯ источни-
ка (т. е. адрес 002642 источника), он вслед за этим автоматически увеличива-
ет c(R4) на 2. Таким образом, в данный момент c(R4) = 002644. В этом
месте ЦП может осуществить доступ к числу, подлежащему пересылке, а
именно, к 147306. Приемником операции MOV является ячейка памяти, ад-
рес которой находится в настоящее время в R4, т. е. 002644. Затем c(R4)
опять автоматически увеличивается на 2, до 002646. Поэтому мы находим,
что адрес источника есть 002642, его содержимое — 147306, а адрес приемни-
ка — 002644. В конце выполнения инструкции имеем: c(R4) = 002646,
с (002642) = 147306 и с (002644) = 147306. Каким бы странным этот пример
ни показался (а он или, по крайней мере, инструкции с аналогичной структу-
рой на самом деле не столь уж возмутительны), он выявляет, в какой имен-
но момент в процессе выполнения инструкции ЦП увеличивается содержи-
мое регистра. Для программиста это может иметь очень большое значение.
Хотя мы рассматривали этот режим адресации в программе сложения из
гл. 5 и убедились в его полезности, не вредно будет снова обсудить принцип
использования режима адресации с автоувеличением. Предположим, что у нас
есть набор чисел в последовательных ячейках памяти. Переслав адрес перво-
го из этих чисел в какой-то регистр, например в R3, и затем обращаясь к R3 в
режиме автоувеличения, мы можем пройти по очереди по последовательным
числам. Обращение к (R3), будет, конечно, обращением к первому из чисел.
Но при (R3)+ произойдет не только обращение к первому числу, но и такая
автоматическая подстройка R3, что он станет указывать на второе число,
точнее, после автоматического увеличения R3 будет содержать адрес второго
числа. Обратите внимание, что это утверждение справедливо независимо от
того, рассматриваются ли числа как полные слова, когда обращение к R3 осу-
ществляется инструкцией пословного типа, или как отдельные байты, когда
обращение к R3 выполняется инструкцией байтового типа. Это происходит
автоматически из-за соответственного увеличения на 2 или на 1.
Приведем последний пример (рис. 75.1). Предположим, что у нас есть
семь 16-битовых чисел в последовательных ячейках памяти, и мы присваива-
113
1 000000 005000 START: CLR R0 .СЧЕТЧИК ’БОЛЬШЕ ЧЕМ’
2 000002 012701 MOV *6’R1 .СЧЕТЧИК ПАР
000006
3 000006 012703 MOV ♦VECTOR,R3 .АДРЕС 1-Г0 ЧИСЛА
000026’
4 000012 022313 LOOP: CMP (R3)+r<R3) .СРАВНИТЬ ЧИСЛО СО СЛЕД, числом
5 000014 003401 BLE CONT .МЕНЬШЕ ЧЕМ ИЛИ РАВНО»
6 000016 005200 INC R0 :УВЕЛИЧИТЬ СЧЕТЧИК НА 1
7 000020 005301 CONT: DEC R1 ;ВЫПОЛНЯТЬ ДЛЯ СЛЕД. ПАРЫ?
в 000022 001373 BNE LOOP ,-ДА — ВЫПОЛНИТЬ ДЛЯ СЛЕД. ПАРЫ
? 000024 000000 HALT :нет — стоп
10
11 000026 000017 VECTOR: 000017 ;ЧИСЛА,
12 000030 102333 102333 5 КОТОРЫЕ
13 000032 002110 002110 ; должны
14 000034 000644 000644 ! БЫТЬ
15 000036 000023 000023 ; СРАВНЕНЫ
16 000040 007441 007441 ! ПО
17 000042 100031 100031 ; ПАРАМ
18
19 000000’ .END START
Рис. 7.5.1
ем первой ячейке символьное имя VECTOR. Мы хотим найти, сколько чи-
сел больше, чем их непосредственные последователи в списке. Таким обра-
зом, если, например, имеются числа, расположенные в заданном порядке,
000017 102333 002110 000644 000023 007441 100031
то четыре из них больше своих последователей. В частности, 000017 больше,
чем 102333 (поскольку 102333 отрицательное), 002110 больше, чем 000644,
000644 больше, чем 000023, и 007441 больше,чем 100031. На рис. 7.5.1 при-
ведена полная программа, которая решает эту несложную задачу.
Обратите внимание, что в строке 1 регистр R0 сбрасывается в 0, чтобы слу-
жить счетчиком для числа пар, в которых первое число больше второго. И хо-
тя имеется семь чисел, R1 устанавливается равным 6 (в строке 2), поскольку
последовательных пар чисел только шесть. Затем символьный адрес VECTOR,
значение которого равно 000026, пересылается в R3. Сердцевиной програм-
мы является строка 4: CMP (R3)+, (R3).
Первый раз, когда выполняется эта инструкция, ЦП находит содержимое
R3 (000026), чтобы получить адрес первого из двух чисел, подлежащих срав-
нению. Затем он автоматически увеличивает содержимое R3 до 000030, при-
чем происходит увеличение на 2, поскольку СМР — это пословная инструк-
ция. Поэтому первым числом, подлежащим сравнению, будет 000017. Затем
ЦП опять использует содержимое R3 как адрес второго сравниваемого числа.
Как мы видим, этот адрес равен 000030, и ЦП осуществляет теперь доступ ко
второму сравниваемому числу, т. е. к 102333. Но обратите внимание, что для
второго операнда R3 используется в режиме 1, а не в режиме 2. Поэтому при
втором обращении к R3 автоувеличения не происходит. Следовательно,
c(R3) все еще равно 000030. Теперь выполняется сравнение, при котором об-
наруживается, что результат положительный. Поэтому инструкция BLE в
строке 5 не выполняется* содержимое R0 увеличивается, содержимое R1
уменьшается, и мы возвращаемся назад, к инструкции СМР. Вспоминая, что
R3 содержит 000030, мы видим, что теперь второе число будет сравниваться
с третьим и т. д.
Этот небольшой пример не только выявляет большую полезность режима
автоувеличения для пошагового прохождения по ячейкам памяти, но и со
114
всей очевидностью показывает необходимость точного понимания, в какой
именно момент происходит автоувеличение: правильность выполнения ин-
струкции СМР в строке 4 базируется на том факте, что это увеличение проис-
ходит непосредственно после доступа к регистру за получением адреса числа,
а не после выполнения всей инструкции.
4. Режим 3 (011)
Название: Косвенный режим автоувеличения
Символ: @(Rn)+
Этот режим адресации подобен режиму 2 в отношении использования ав-
гоувеличения содержимого указанного регистра, но различия между ними
существенны. Первое из них заключается в способе вычисления ЭЯ. Вспом-
ним, что в режимах 1 и 2 ЭЯ была ячейкой памяти,адрес которой находился
в заданном регистре. В режиме 3 ЭЯ определяется следующим образом. Чис-
ло в указанном регистре используется как адрес некоторой ячейки памяти;
содержимое этой ячейки памяти берется затем как адрес ЭЯ. Пример прояс-
нит сказанное.
Предположим, что c(Rl) = 024460 и с(024460) = 102336. Тогда, если R1
используется в режиме 3, эффективной ячейкой будет байт или слово, адрес
которого есть 102336. Читателю должно быть понятно, что здесь происхо-
дит — вычисление адреса ЭЯ откладывается на один дополнительный шаг по
сравнению с режимами 1 и 2.
Второе различие между режимами 3 и 2 заключается в том, что, хотя со-
держимое указанного регистра увеличивается сразу же после его использо-
вания для получения адреса, это увеличение всегда происходит на 2 и нико-
гда на 1, даже есДи инструкция — байтового типа (MOVB, DECB, COMB и т. д.),
и не зависит от того, какой регистр указан (от R0 до R7). Это требует неко-
торого пояснения. Вспомним, что адрес — это число, которое может быть не-
четным (если это адрес старшего байта какого-то слова) или четным (и то-
гда это или адрес полного слова, или адрес младшего байта слова). Но в лю-
бом случае адрес - это всегда 16-битовое слово. Введем здесь некоторую не-
формальную символику. Предположим, что c(R4) = X, где X - четное, и
с(Х) = У, которое может быть или четным или нечетным. Рассмотрим ин-
струкцию CLRB @(R4)+. Подлежащий очистке байт будет вычисляться сле-
дующим образом. Содержимое R4, а именно X, будет использовано как ад-
рес (содержимое слова, адрес которого есть X, а именно У, будет использо-
ваться как адрес байта, подлежащего очистке). Пока все идет хорошо. Но
предположим теперь, что, поскольку CLRB — байтовая инструкция, ЦП авто-
матически увеличил содержимое R4 на 1. Тогда к моменту, когда осуще-
ствляется доступ к R4 для определения адреса ЭЯ, c(R4) будет равным Х +
+ 1, т. е. нечетным. Можно ли теперь обращаться к R4 опять в режиме 3? Ко-
нечно, нет — по следующей причине: его содержимое, X + 1, будучи нечетным,
не может быть адресом слова, содержимое которого является адресом ЭЯ.
Таким образом, R4, содержимое которого увеличено на 1, сразу же потерял
свою эффективность для пошагового прохождения по последовательным ад-
ресам. По этой причине в режиме 3 всегда происходит автоматическое увели-
чение на 2.
Из-за двойной косвенности при вычислении ЭЯ читатель может законно
усомниться в полезности этого режима адресации. И действительно,в повсед-
115
невном программировании этот режим встречается не так уж часто. Более то-
го, мы даже отказываемся привести пример, поскольку в этом месте нашего
изложения такой пример был бы весьма похож на оправдание. Следует, одна-
ко, заметить, что существуют такие обстоятельства, при которых этот режим
является как раз тем, что нужно, и без него потребовалось бы более утоми-
тельное программирование.
5. Режим 4 (100)
Название: Режим автоуменьшения
Символ: — (Rn)
Режим автоуменьшения аналогичен режиму автоувеличения (режиму 2)
в том, что заданный регистр содержит адрес ЭЯ. Однако есть два существен-
ных отличия. Во-первых, как следует из названия, содержимое регистра
уменьшается, а не увеличивается. Во-вторых, уменьшение содержимого реги-
стра происходит прежде его использования в качестве адреса ЭЯ (вспомина-
ем, что в режиме 2 содержимое регистра увеличивалось вслед за его исполь-
зованием в качестве адреса). Для байтовых инструкций происходит умень-
шение на 1, для пословных инструкций — на 2. Регистры R6 и R7 (PC) опять
являются исключениями — всегда происходит уменьшение на 2 (однако см.
сноску на с. 112).
Как и в случае автоувеличения, очень важен момент уменьшения; поэтому
в качестве еще одного напоминания программисту используется содержатель-
ная нотация. Обратите внимание, что в режиме 2 мы использовали символ
(Rn)+, где знак ’’плюс” размещался вслед за символом регистра для указа-
ния того, что увеличение происходит после доступа к содержимому регистра.
В режиме 4 мы помещаем знак ’’минус” перед символом регистра, чтобы по-
казать, что уменьшение происходит до того, как из регистра берется адрес
ЭЯ. Дадим простой пример для сравнения этих двух режимов.
Предположим, что c(R0) = 002306, и рассмотрим две инструкции INC
(R0) + и INC — (R0). В первом случае адрес ЭЯ будет 002306, и после опреде-
ления этого адреса c(R0) будет увеличено до 002310. Во втором случае c(R0)
будет сначала уменьшено до 002304 и затем использовано как адрес ЭЯ. В
силу этих обстоятельств режим 2 иногда называют режимом последующего
автоувеличения, а режим 4 — режимом предварительного автоуменьшения.
Основное использование режима адресации 4 опять состоит в том, чтобы
дать возможность регистру последовательно пройти но адресам памяти, но на
этот раз регистр проходит их в обратном направлении — от верхних адресов
к нижним. Здесь требуется определенное внимание. Это следует из образца
программы, который показан на рис. 7.5.2 и представляет собой модифика-
цию программы на рис. 7.5.1, подсчитывающей пары чисел, где первое число
больше второго. Здесь числа просматриваются в обратном порядке — от кон-
ца к началу.
Обратите внимание, что мы произвели несколько модификаций. Теперь
VECTOR представляет символьный адрес не первого, а последнего из чисел.
Мы также изменили инструкцию сравнения в строке 4, поскольку сравнение
происходит в обратном порядке. И вследствие этой перемены мы изменили в
строке 6 инструкцию BGE CONT. (Почему?).
1 000000 005000 START: CLR R0 ?СЧЕТЧИК ’БОЛЬШЕ ЧЕМ’
2 000002 012701 NOV ♦6.R1 .СЧЕТЧИК ВАР
000006
3'000006 012703 MOV «VECTOR.R3 ;АДРЕС ПОСЛЕДНЕГО ЧИСЛА
000042’
4 000012 021343 LOOP: CMP <R3).-<R3) ;СРАВНИТЬ ЭТО ЧИСЛО С
5 ; ПРЕДЫДУЩИМ ЧИСЛОМ
6 000014 002001 BGE CONT ;БОЛЬШЕ ЧЕЙ ИЛИ РАВНО>
7 000016 005200 INC R0 ;УВЕЛИЧИТЬ СЧЕТЧИК НА 1
8 000020 005301 CONT: DEC Rl ;ВЫПОЛНЯТЬ ДЛЯ СЛЕД. ВАРЫ?
9 000022 001373 BNE LOOP ;ДА — ВЫПОЛНИТЬ ДЛЯ СЛЕД. ВАРЫ
10 000024 000000 HALT «НЕТ — СТОП
И
12 000026 000017 000017 > ЧИСЛА.
13 000030 102333 102333 ; КОТОРЫЕ
14 000032 002110 002110 ; Д0Л1НЫ
15 000034 000644 000644 ; быть
16 000036 000023 000023 ; СРАВНЕНЫ
17 000040 007441 007441 ; ПО
18 000042 100031 VECTOR: 100031 ; ПАРАМ
19 >
20 000000’ .END START
Рис. 7.5.2
6. Режим 5 (101)
Название: Косвенный режим автоуменьшения
Символ: @ — (Rn)
Пока что читателю довольно трудно угадать, что происходит в этом режи-
ме. Содержимое регистра сначала уменьшается на 2 (никогда на 1, даже для
байтовых инструкций), затем (новое) содержимое используется как адрес
слова памяти, содержимое которого затем используется как адрес ЭЯ. Итак,
здесь нет ничего удивительного и опять не видно особой пользы от этого ре-
жима для обычных ситуаций программирования. Мы предоставляем читате-
лю придумать свои собственные примеры вычисления ЭЯ, а также проверить
свое понимание этого режима, попробовав выполнить несколько упражне-
ний, приведенных в конце главы, в которых используется этот режим.
7. Режим 6 (110)
Название: Индексный режим
Символ: X (Rn)
Как мы увидим, этот режим адресации оказывается весьма и весьма полез-
ным. Символ регистра заключен в круглые скобки — (Rn) — и это наводит
на мысль, что регистр содержит адрес ЭЯ, что почти правильно. Но получив
содержимое регистра, ЦП прибавляет к нему число X, называемое индексом.
Здесь важно заметить, что содержимое регистра при таком сложении ЦП не
изменяет. Полученное в результате сложения число используется как адрес
ЭЯ. Приведем простой пример.
Предположим, что c(R5) = 026301, и рассмотрим инструкцию COMB
-12(R5). Для определения адреса байта, подлежащего дополнению, мы бе-
рем содержимое R5 (026301) и прибавляем индекс X, равный в этом случае
-12. Получаем 026301 + (—12) = 026267. Это адрес байта, который будет за-
менен своим дополнением до единицы (после чего у нас все же остается
c(R5) = 026301). Таким образом, адрес ЭЯ в индексированной инструкции
есть индекс плюс содержимое регистра, т. е. X + c(Rn).
Полезным будет также более расширенный пример. Вспомним, что в гл. 6
мы потратили некоторое время на написание программы для нахождения на-
116
117
1 .TITLE MAXIMUM
2 3 000000 012700 STARTs 000004 MOV »4,R0 ;СРАВНИВАТЬ 4 РАЗА
4 000004 012702 000072’ MOV SDATA.R2 :АДРЕС 1-Г0 ЧИСЛА В R2
5 000010 012205 MOV <R2)+,R5 Я-Е ЧИСЛО = ВРЕМ. МАКСИМУМ
6 000012 020522 LOOP; CMP R5»(R2) + СРАВНИТЬ ВРЕМ. МАКСИМУМ С ЧИСЛОМ
7 000014 002002 BGE NEXT ;ВРЕМ. МАКСИМУМ БОЛЬШЕ — ПРОПУСТИТЬ
8 000016 016205 MOV -2<R2)»R5 ‘ЗАМЕНИТЬ ВРЕМ. МАКСИМУМ
177776 9 000022 005300 NEXT: DEC R0 :УМЕНЬШИТЬ СЧЕТЧИК НА 1
10 000024 001372 BNE LOOP ?СЛЕДУВШЕЕ ЧИСЛО» ЕСЛИ НЕ НОЛЬ
И 000026 010567 000036 12 000032 13 000066 MOV R5»ANSWER >ПОЛУЧИТЬ МАКСИМУМ XOUT.OCT «ANSWER»»! 5 И РАСПЕЧАТАТЬ ЕГО «EXIT sВЕРНУТЬСЯ К ОПЕРАЦИОННОЙ СИСТЕМЕ
14 15 000070 ANSWERS 16 17 18 'blKW 1 ;ДЛЯ РАСПЕЧАТКИ МАКСИМУМА 5ДОЛПЕН БЫТЬ НАЙДЕН МАКСИМУМ СРЕДИ СЛЕДУЮЩИХ 5 ЧИСЕЛ
19 000072 000403 DATA: 20 000074 102304 .WORD 403 .WORD 102304
21 000076 023704 22 000100 007702 23 000102 176337 24 25 000000’ .WORD 23704 .WORD 7702 .WORD 176337 .END START
Рис. 7.5.3
ибольшего из пяти чисел, и одним из решений, которые мы там приняли, бы-
ло не увеличивать автоматически (в режиме 2) содержимое регистра 2, пред-
ставляющее собой адрес числа, используемого для сравнения с временным
максимумом. Мы обосновывали это следующими соображениями: хотя уве-
личение содержимого регистра было, конечно, желательным, чтобы избежать
необходимости последующего увеличения c(R2) дважды, но, к несчастью,
когда необходимо было заменить временный максимум в R5, авто увеличе-
ние продвигало бы R2 уже за пределы адреса числа, которое должно было за-
менить временный максимум. В то время не видно было способа обойти эту
трудность, но теперь мы понимаем, что улучшить программу позволяет нам
индексный режим. Листинг модифицированной программы мы даем без
последующих комментариев (рис. 7.53), а читателю имеет смысл детально
исследовать его и сравнить с листингом, показанным на рис. 6.7.1.
Прежде чем перейти к последнему режиму адресации, потратим несколько
минут на изучение кода, сгенерированного ассемблером для индексирован-
ной инструкции, и того, как ЦП поступает при выполнении такой инструк-
ции. Как типовой пример рассмотрим инструкцию CLR 5 (Rl),‘которая ас-
семблируется следующим образом (адреса выбраны произвольно):
026442 005061 Операция очистки
026444 000005 Индекс 5
Обратите внимание, что была построена двухсловная инструкция с самим
индексом во втором из этих слов. Посмотрим теперь подробно, какие дей-
ствия предпринимает ЦП при выполнении этой инструкции. Мы, конечно,
предполагаем, что перед ее выполнением с (PC) = 026442. Центральный про-
цессор производит извлечение, т. е. получает содержимое ячейки памяти, ад-
рес которой находится в программном счетчике, и затем увеличивает содер-
жимое PC на 2; это действие при извлечении выполняется всегда. Теперь
с (PC) = 026444. По битам режима (110) в операторе ЦП узнает, что инструк-
ция должна быть индексированной, следовательно, индекс находится в ячей-
ке памяти, адрес которой представлен в настоящее время в PC. Чтобы полу-
чить индекс (который необходим, конечно, для вычисления адреса ЭЯ),ЦП
производит еще одно извлечение вместе с сопутствующим^ветшченмем с (PC)
на 2. Обратите внимание, что PC теперь указывает на следующую инструкцию
программы. Может показаться, что мы уделяем слишком большое внимание
этому второму извлечению, но в следующем разделе мы убедимся в его
значении.
8. Режим 7 (111)
Название: Косвенный индексный режим
Символ: @X(Rn)
Опыт изучения предыдущих семи режимов адресации, вероятно, позволит
читателю угадать, что символ @ в режиме адресации 7 показывает, что полу-
чение адреса ЭЯ откладывается еще на один шаг, и это правильно. Адрес ЭЯ
вычисляется так: берется содержимое регистра, к нему прибавляется индекс,
и результат затем используется как адрес ячейки памяти, содержимое кото-
рой является адресом ячейки, над которой выполняется операция.
Полезным будет конкретный пример.
Предположим, что c(R2) = 042662, X = 000012, с (042674) = 112002 и
с(112002) = 173353. Рассмотрим инструкцию NEG @12(R2). Прибавляя ин-
декс к c(R2), получаем 000012 + 042662 = 42674. Содержимое ячейки па-
мяти с адресом 042674 является адресом ЭЯ. Таким образом, ЭЯ имеет ад-
рес 112002, и это есть 16-битовое слово 173353 (содержимое адреса 112002),
знак которого подвергается изменению. Когда выполнение инструкции за-
вершается, мы имеем c(R2) = 042662, с(042674) = 112002 и с(112002) =
-004425 (-173353).
Здесь требуется сделать некоторое предостережение об использовании ре-
жима 7. Поскольку сумма индекса и содержимого указанного регистра ис-
пользуется как адрес слова, содержимое которого затем используется в ка-
честве адреса ЭЯ, очевидно, что сумма X + c(Rn) должна быть четной. Хотя
из этого не следует, что и Хи (Rn) обязаны быть четными, подразумевается,
что если одно из слагаемых нечетное, то и другое должно быть нечетным.
Здесь кроются потенциальные проблемы программирования, и режим 7 дол-
жен использоваться с определенной степенью внимания. Это предостережение
не распространяется на режим 6, хотя определенное внимание требуется и
здесь. В режиме 6 X + c(Rn) может быть нечетным, если использующая его
инструкция является байтовой.
Прежде чем перейти к следующему разделу, дадим два последних коммен-
тария по поводу индексированных инструкций. Во-первых, обратите внима-
ние на то, что как в режиме 6, так и в режиме 7 содержимое указанного ре-
гистра при выполнении инструкции не изменяется. Поэтому можно варьиро-
вать адрес ЭЯ без воздействия на содержимое регистра (сравните эту ситуа-
цию с режимами 2 — 5, где содержимое регистра изменяется). Во-вторых,
читатель иногда увидит мнемоники, содержащие символ регистра @(Rn).
Обзор рассмотренных режимов адресации со всей очевидностью показывает,
что такого режима не существует (да и не может существовать, поскольку
118
119
мы уже познакомились со всеми возможными режимами — прошли по всем
комбинациям битов режима). Например, в программе может встретиться
мнемоника COM @(R3). Ассемблер интерпретирует эту конструкцию как за-
пись COM @0(R3), т. е. подразумевает здесь режим 7 с индексом 0. Действие
конечно, состоит в прибавлении индекса 0 к содержимому R3 и использовании
c(R3) в качестве адреса ЭЯ. Таким образом, в действительности мы.создали
(с помощью ассемблера) некоторый режим адресации, близкий к @(Rn) +
или к (Rn), но без автоматического увеличения или уменьшения. И хотя
он может оказаться полезным, мы видим, что здесь нет ничего нового — это
режим 7 с индексом 0. Ассемблер просто настолько любезен, что транслирует
нашу нотацию соответствующим образом. Но важно понимать, что ассемблер
в данном случае генерирует двухсловную инструкцию, второе слово которой
есть индекс — 000000.
Восемь регистровых режимов адресации ЭВМ PDP-11, обсужденных в этом
разделе, мы свели в табл. 7.5.4.
Таблица 7.5.4
Режимы адресации PDP-11
Режим Символ Название Эффективная ячейка (ЭЯ)
0 Rn Регистровый Регистр Rn
1 (Rn) Косвенный реги- стровый с (Rn) = адрес ЭЯ
2 (Rn)+ С автоувеличением с (Rn) = адрес ЭЯ. Затем с (Rn) увеличивает- ся на 1 (если байтовая инструкция) или на 2 (если пословная инструкция); с (R6) и с (R7) всегда увеличиваются на 2 (см. ссыл- ку на с. 112)
3 @(Rn)+ Косвенный с автоуве- личением с (Rn) = адрес ЭЯ. Затем с (Rn) увеличивает- ся, всегда на 2
4 -(Rn) С автоуменьшением с (Rn) уменьшается на 1 (если байтовая ин- струкция) или на 2 (если пословная ин- струкция) . Затем с (Rn) используется как адрес ЭЯ; c(R6) hc(R7) всегда увеличива- ется на 2 (см. ссылку на с. 112)
5 @ -(Rn) Косвенный с авто- уменьшением с (Rn) уменьшается, всегда на 2. Затем с (Rn) используется как адрес ЭЯ
6 X(Rn) Индексный X + с (Rn) = адрес ЭЯ
7 @ X(Rn) Косвенный индекс- ный X + с (Rn) = адрес адреса ЭЯ
7.6. СПЕЦИАЛЬНЫЙ СЛУЧАЙ - РЕЖИМЫ АДРЕСАЦИИ С РЕГИСТРОМ 7
(ПРОГРАММНЫМ СЧЕТЧИКОМ)
В предыдущем разделе мы сознательно устранили R7 из рассмотрения из
за особенностей его поведения во время извлечения. Чтобы понять, почем)
R7 должен рассматриваться как специальный случай, предположим, что неко
торая инструкция обращается к R7, например INC (R7) [или INC (PC)]
Предположим далее, что эта инструкция загруженав память по адресу 023104
023104 005217 INC (R7)
023106 -------
120
Чтобы эта инструкция увеличения могла быть выполнена, PC должен содер-
жать число (адрес) 023104. Если это так, то ЦП использует это число как ад-
рес ячейки памяти, содержащей следующую инструкцию, подлежащую вы-
полнению, получит содержимое этой ячейки (инструкцию увеличения 005 217)
и, прежде чем декодировать инструкцию, увеличит содержимое R7 на 2, т. е.
произведет действие, которое ЦП всегда выполняет при извлечении инструк-
ции. Использование программного счетчика наподобие регистров R0 — R6
усложняется тем, что к моменту, когда инструкция извлечена, PC уже не име-
ет того значения, которое у него было перед извлечением. Тем не менее про-
। раммный счетчик можно использовать в инструкциях точно так же, как дру-
। ие регистры, при условии, что мы знаем об этом специальном свойстве и
учитываем его при построении инструкций, работающих с PC. Таким обра-
и)м, INC (PC) — совершенно законная инструкция, хотя, как мы увидим,
применение режима адресации 1 к PC порождает при выполнении инструкции
некоторые странные результаты и поэтому не очень полезно.
Поскольку к R7 в равной степени применимы различные режимы адреса-
ции, познакомимся с каждым из них — от 0 до 7. Мы увидим, что некоторые
из них дают очень желательные эффекты, тогда как другие при выполнении
приводят к весьма странным последствиям, что.делает их полностью беспо-
лезными, за исключением крайне специальных случаев.
а) Адресация с программным счетчиком, режим 0 (000).
Этот режим адресации с PC может быть весьма полезным, но, если ему уде-
ляется недостаточное внимание, может приводить к неприятностям. Чтобы
понять, почему это так, вспомним, что PC может использоваться или какре-
гистр-источник, или как регистр-приемник инструкции (или в обеих ролях).
В первом случае содержимое PC не будет изменяться, тогда как во втором
случае будет. А мы должны знать, что если содержимое PC изменяется, то мы
вносим (вероятно, намеренно) возмущение в ход выполнения программы.
Чтобы представить себе, что происходит правильно, а что неверно, рассмот-
рим несколько простых примеров. Во всех случаях мы полагаем, что ин-
струкция загружается, начиная с адреса 004602, и что непосредственно перед
ее выполнением PC содержит этот адрес:
004602 010702 MOV PC, R2
004604 -------
Эта инструкция совершенно понятна — она пересылает содержимое PC в
R2. Но нам необходимо знать, что к моменту, когда инструкция будет извле-
чена, с (PC) будет увеличено до 004604. Поэтому в R2 будет переслано число
004604, а не 004602. Кроме того, что с (PC) увеличивается на 2 при извлече-
нии инструкции, инструкция на него никак больше не воздействует.
004602 060712 ADD PC, (R2)
004604 -------
Это тоже инструкция с хорошим поведением, которая опять не оказывает
действия на с(РС). Она прибавляет содержимое PC (т. е. 004604) к числу,
адрес которого находится в R2, и помещает сумму в слово с этим адресом.
Приведем еще один пример, представляющий весьма обычную и полезную
инструкцию. В деталях читателю предлагаем разобраться самостоятельно.
121
004602 010746 MOV PC,-(R6)
004604 ------
Эти три примера были весьма простые, с (PC) в них оставалось без измене-
ния (за исключением обычного увеличения на 2 из-за извлечения инструк-
ции) . Но давайте рассмотрим некоторые примеры, в которых PC является
приемником инструкции.
004602 010207 MOV R2,PC
004604 -------
В этом примере при извлечении инструкций ЦП увеличивает с (PC) до
004604. Теперь инструкция выполняется — содержимое R2 пересылается в
PC. Чтобы понять эффект этого действия, предположим, что c(R2) =045532.
Поэтому инструкция MOV пересылает число 045532 в PC, перекрыв то зна-
чение, которое там было, а именно 004604. Теперь, когда выполнение ин-
струкции подходит к завершению, ЦП готов к извлечению, декодированию и
выполнению следующей инструкции. Где он ищет ее адрес? Конечно, в PC.
И то, что он там видит, есть 045532, а не 004604. Тогда он извлекает содержи-
мое 045532, увеличивает содержимое PC до 045534 и начинает следующий
цикл декодирования-выполнения. Таким образом, эта инструкция прерыва-
ет нормальную последовательность выполнения и передает управление в
ячейку 045532, т. е. происходит переход к 045532.
Такой ’’переход” будет происходить всякий раз, когда инструкция моди-
фицирует число в программном счетчике. Конечно, если это не предусмотре-
но, то могут возникать нежелательные эффекты, ибо неожидаемое изменение
PC приводит к непредсказуемым результатам. В то же время мы напомина-
ем, что воздействие инструкции на PC не всегда бывает плохим. И действи-
тельно, инструкции условного ветвления делают именно то, что описано, —
они прибавляют некоторое число к содержимому программного счетчика,
чтобы породить ветвление и передать управление к некоторой другой части
памяти.
Если же подстройка содержимого PC желательна,то она должна выполнять-
ся с должным вниманием. Рассмотрим, например, следующую инструкцию:
004602 060307 ADD R3,PC
004604 -------
Действие этой инструкции состоит в прибавлении содержимого R3 к содер-
жимому PC и в помещении результирующей суммы в PC. К моменту начала
выполнения инструкции содержимое PC будет равно 004604. Предположим,
однако, что c(R3) = 000423. Тогда PC получит значение 000423 + 004604 =
= 005227. Но теперь ЦП не в состоянии извлечь следующую подлежащую вы-
полнению инструкцию, поскольку PC не содержит адрес слова - его содер-
жимое нечетное.
Познакомившись с несколькими примерами Инструкций, мы не исчерпали
всех возможных случаев; мы не сможем этого сделать также и при обсужде-
нии остальных семи режимов адресации с PC. Но читатель, хорошо разбираю-
щийся в этих режимах адресации, будет без труда понимать действия, пред-
принимаемые ЦП, когда используется регистр R7.
б) Адресация с программным счетчиком, режим 1 (001).
122
Для определения эффекта от применения режима адресации 1 с PC, будет
полезно вычислить ЭЯ для использующей его инструкции. Предположим, что
с (PC) = X. Когда инструкция извлекается из X, содержимое PC увеличивает-
ся до X + 2. Тогда, поскольку PC адресуется в режиме 1, эффективной ячей-
кой будет слово (или, возможно, байт), адрес которого находится в PC. То
есть адресом ЭЯ будет X + 2. Это особая ситуация, и, чтобы проиллюстриро-
вать, что здесь происходит, рассмотрим пример.
Предположим опять, что с (PC) = 004602 и что код инструкции INC (PC)
находится в 004602. Допустим, что в ячейке 004604 расположена инструкция
CLR R3.
004602 005217 INC (PC)
004604 005003 CLR R3
004606 -------
При выполнении ЦП извлечет содержимое ячейки 004602, увеличит содержи-
мое PC до 004604 и затем выполнит инструкцию INC (PC). Какой будет ЭЯ
этой инструкции? Ответ: слово, адрес которого находится в R7. Поскольку
c(R7) = 004604, слово в ячейке памяти с этим адресом, а именно 005003, бу-
дет увеличено до 005004. Таким образом, в этот момент с (004604) = 005004.
Теперь, когда ЦП выполнил инструкцию увеличения, он готов к выполнению
следующей инструкции. Он обращается к PC за адресом следующей инструк-
ции, находит 004604, извлекает содержимое этой ячейки (005004), увеличи-
вает с (PC) до 004606 и выполняет только что извлеченную инструкцию. Но
это будет инструкция CLR R4, а не CLR R3, поскольку код инструкции был
увеличен.
Таким образом, представляется, что если PC используется в режиме 1 для
указания приемника инструкции, то происходят какие-то странные и, вероят-
но, не очень желательные вещи. Даже если PC используется в режиме 1 в ка-
честве регистра-источника инструкции, результаты не очень интересны, хотя
есть несколько случаев, в которых они могут оказаться подходящими. Од-
нако прежде, чем отвергнуть режим адресации 1 с PC как бесполезный, давай-
те рассмотрим еще один пример.
Предположим, что мы хотим переслать, например, число 224 в регистр 1.
Рассмотрим следующий машинный код:
004602 011701 MOV (PC),R1
004604 000224 (число 224)
004606 -------
Посмотрим, что произойдет, когда с (PC) = 004602 и ЦП готов к выполнению
инструкции. Центральный процессор использует содержимое PC в качестве
адреса следующей инструкции, извлекает число и увеличивает с(РС) до
004604. Текущее содержимое PC, а именно 004604,является адресом ЭЯ ис-
точника. Таким образом, 000224 — содержимое ячейки 004604 — будет тем
числом, которое подлежит пересылке. Приемником, конечно, является R1.
Поэтому действие инструкции состоит в пересылке числа 000224 в R1 — т. е.
именно то, чего мы хотели. Но здесь появляются проблемы. Теперь ЦП готов
перейти к следующей инструкции и извлекает содержимое ячейки памяти,
адрес которой находится в PC, увеличивая с (PC) до 004606. Извлеченное и,
таким образом, подлежащее выполнению 16-битовое число будет 00224. К
123
несчастью, оно не может быть декодировано, поскольку эта битовая конфи-
гурация не является кодом какой-либо инструкции ЭВМ PDP-11.
Мы почти открыли полезную конструкцию — такую, в которой источни-
ком является заданное число. Трудность, с которой мы здесь столкнулись,
можно было бы преодолеть, если бы ЦП после получения этого заданного чи-
сла еще раз увеличил с (PC) на 2, чтобы к моменту завершения выполнения
текущей инструкции PC указывал на следующую инструкцию, а не на это за-
данное число. То, что нам здесь необходимо, — это режим адресации 2.
в) Адресация с программным счетчиком, режим 2 (010).
Как нам уже известно, режим адресации 2 аналогичен режиму адресации
1 за исключением того, что в режиме 2 после доступа к регистру для получе-
ния адреса ЭЯ1 содержимое этого регистра увеличивается. Кроме того, в слу-
чае регистра R7 (PC) всегда происходит увеличение на 2, независимо от того,
была ли инструкция байтовой или пословной. Давайте заново рассмотрим
пример инструкции режима 1, используя на этот раз режим адресации 2 с PC.
004602 012701 MOV (PC)+,R1
004604 000224 (число 224)
004606 --------
Мы предполагаем, что первоначально с (PC) = 004602, ЦП извлекает содер-
жимое ячейки с этим адресом (012701) иувеличиваетс(РС) до 004604. Опять
источником MOV является число, адрес которого находится в PC, а именно
число 000224. Но так как используется режим адресации 2, когда ЦП из PC
получает адрес 004604, с(РС) автоматически увеличивается до 004606. Те-
перь выполняется инструкция MOV, число 000224 помещается в регистр R1,
а когда ЦП готов перейти к следующей инструкции, он находит, что PC ука-
зывает на нее независимо от того, что находится в 004606. Мы достигли же-
лаемого эффекта. Используя режим 2, нам удалось продвинуть PC дальше чи-
сла, подлежащего пересылке.
Аналогично читатель может проверить, что следующий код будет успешно
прибавлять число 177776 (—2) к числу, адрес которого находится в R5:
004602 062715 ADD (РС)+, (R5)
004604 177776 (число -2)
004606 --------
Хотя этот режим адресации полезен, когда в качестве источника инструк-
ции используется число, он тем не менее не очень удобен, поскольку програм-
мисту необходимо помнить о том, что надо использовать режим адресации 2
с PC и затем вставлять число (которое будет, например, пересылаться, при-
бавляться или вычитаться) в слово, следующее за самой инструкцией режима
2. В то же время не столь уж трудно заставить ассемблер генерировать этот
код — использовать режим адресации 2 с PC и затем помещать заданное число
в следующее слово. Но для этого нужно как-то указать ассемблеру, что мы
намереваемся делать. Мы достигаем этого, заменяя используемый источник,
т. е. (РС)+, самим числом, перед которым ставится знак номера. Таким обра-
зом, две предыдущих инструкции могут быть переписаны соответственно так:
MOV #224, R1 и ADD #177776, (R5). Конечно, эта нотация не новая, по-
скольку мы ее уже использовали — в каждой (мнемонической) программе
124
мы пересылали некоторое абсолютное число в один или другой регистр (бу-
дет неплохо, если читатель обратится к предыдущим программам, чтобы по-
смотреть, как использовалась эта конструкция и какой код генерировал ас-
семблер). Поскольку этот режим адресации оказывается настолько полез-
ным (мы даже изобрели для него специальную символику, распознаваемую
ассемблером), то ему дается отдельное название — непосредственный режим
адресации с программным счетчиком.
г) Адресация с программным счетчиком, режим 3 (011).
Вспомним, что в предыдущем разделе мы отвергли режим 3 регистровой
адресации (косвенный с автоувеличением) как не очень полезный для повсе-
дневного программирования. Однако, когда заданным регистром является
R7, т. е. программный счетчик, мы увидим, что этот режим адресации оказы-
вается весьма и весьма желательным. Напоминаем, что в режиме 3 содержи-
мое заданного регистра используется как адрес адреса ЭЯ и затем автомати-
чески увеличивается на 2.
Предположим опять, что с (PC) = X, и что с(Х) — это некоторая инструк-
ция, обращающаяся к PC в режиме 3, а ячейка — X +2 содержит число У. Ко-
гда инструкция, содержащаяся в X, извлекается, с (PC) увеличивается до X +
+ 2. После этого ЭЯ инструкции вычисляется следующим образом. Текущее
содержимое PC, а именно X + 2, содержит число (У), которое является адре-
сом ЭЯ. Кроме того, с(РС) автоматически увеличивается на 2 до X + 4. При-
мер, в котором используется инструкция проверки (TST), будет способство-
вать нашему пониманию (подробности об этой инструкции смотрите в при-
ложении А):
004602 005737 TST @(РС)+
004604 036224 (число 036224)
004606 -------
Число 036224 в ячейке 004604 выбрано произвольно. В его значимости мы
сейчас убедимся.
Если мы положим, что с (PC) = 004602, то ЦП извлечет число 005737 и
увеличит содержимое PC до 004604. Так как применяется режим адресации
3 с PC (6 бит со значением 37 в инструкции) ЭЯ вычисляется следующим об-
разом. Содержимое регистра — в данном случае PC — используется как адрес
слова, содержимое которого является адресом ЭЯ. Поскольку с (PC) =004604
и с(004604) = 036224, 036224 является адресом ЭЯ, т. е. словом, подлежа-
щим проверке. К этому времени с (PC) также увеличивается до 004606.
Значение этого режима адресации состоит в том, что мы впервые увидели
средство для обращения к ячейке памяти путем указания ее числового адре-
са. До сих пор мы делали это не прямо — помещая адрес в регистр и обраща-
ясь затем к регистру в соответствующем режиме. Здесь же мы можем дей-
ствительно указать сам адрес — или числовой, или символьный. Опять, чтобы
снять с себя заботу об указании режима 3 с PC и о помещении адреса в слово,
следующее за инструкцией, мы обращаемся за помощью к ассемблеру. На
месте регистрового обозначения (источника или приемника) @ (РС)+ мы ис-
пользуем нотацию @#, за которой следует адрес. Этот режим адресации назы-
вается абсолютным режимом адресации с программным счетчиком. Таким
образом, пример с инструкцией проверки мы можем записать как TST
125
@#36224, надеясь на то, что ассемблер построит показанный выше код. Ана-
логично, мы можем теперь записывать такие инструкции, как CLR @#1124,
MOV @#DATA, R3, ADD @# 104662, @#NUMBER и т. п. В последнем приме-
ре содержатся два обращения в абсолютном режиме с PC, и мы покажем код,
сгенерированный ассемблером, опять полагая, что первое слово этой ин-
струкции размещается в ячейке 004602. Читателю рекомендуется детально
ознакомиться с этим примером и проверить, что содержимое ячейки памяти
104662 будет прибавляться к содержимому ячейки памяти с символьным ад-
ресом NUMBER.
004602
004604
004606
004610
ADD @# 104662, @#NUMBER
(число 104662))
(числовое значение символа NUMBER)
063737
104662
NUMBER
д) Адресация с программным счетчиком, режим 4 (100).
Режим адресации 4 с PC обладает двумя характеристиками, которых до-
статочно, чтобы сделать его почти бесполезным. Во-первых, когда PC содер-
жит адрес инструкции, использующей режим 4 с PC, и эту инструкцию извле-
кает ЦП, с (PC), как обычно, увеличивается на 2. Но поскольку обращение к
PC в самой инструкции происходит в режиме 4, то с (PC) уменьшается на 2,
т. е. возвращается назад к состоянию до обращения к регистру, так что PC
опять будет показывать на операцию; которая к нему обращается. Таким об-
разом, ЭЯ будет ячейкой, занимаемой самой инструкцией, и трудно приду-
мать какие-то обстоятельства, в которых этот режим мог бы оказаться по-
лезным. Во-вторых [если только сама инструкция не использует еще раз ад-
ресацию с PC такую, которая увеличивает с (PC) ], к моменту завершения вы-
полнения инструкции PC все еще будет содержать адрес первоначальной ин-
струкции. Когда ЦП будет готов выполнить ’’следующую” инструкцию, он
произведет другое извлечение и наткнется опять на ту же самую инструкцию,
которую только что обработал, т. е. попадет в бесконечный цикл.
Ясно, что предварительное автоуменьшение с (PC) — действие, которое
нарушает увеличение PC при извлечении центральным процессором — делает
этот режим фактически бесполезным. Поэтому не имеет смысла дальше тра-
тить на него время. Опять мы подчеркиваем, что этот режим не является не-
допустимым — он просто мало полезен.
(Читатель может установить, что инструкция
004602 014702 MOV ~(PC),R2
004604 -------
фактически вводит ЦП в бесконечный цикл. В качестве второго, более за-
нимательного примера читатель может просмотреть несколько первых шагов
инструкции
004602 005247 INC -(PC)
004604 -------
чтобы проследить, насколько быстро ситуация выходит из-под контроля.)
е) Адресация с программным счетчиком, режим 5 (101).
Это опять режим с предварительным автоуменьшением, хотя вычисление
адреса ЭЯ откладывается еще на один дополнительный шаг. Поскольку здесь
126
справедливы (и в большинстве своем по тем же самым причинам) те же воз-
ражения, что и для режима 4, перейдем к следующему режиму.
ж) Адресация с программным счетчиком, режим 6 (110).
Этот режим — индексный — самый интересный и полезный, поэтому мы
изучим его с особым вниманием. Операнд в этом случае имеет вид Х(РС),
где X — индекс. Адрес ЭЯ вычисляется как X + с (PC), т. е. находится путем
смещения PC на величину индекса. Такой способ определения ЭЯ может быть
очень важным. Поскольку его значимость еще не очевидна, рассмотрим де-
тально два примера.
Рассмотрим сначала следующую инструкцию, которая, как опять полага-
ем, загружается, начиная с ячейки 004602:
004602 016703
004604 001442
004606 -------
MOV 1442(PC), R3
Индекс 1442.
Это индексированная инструкция, и ассемблер сгенерировал два слова —
первое содержит операционный код, а второе есть сам индекс. Полагая, что
с (PC) = 004602 и ЦП готов к выполнению инструкции, мы видим, что ЦП из-
влекает 016703 и увеличивает с (PC) в результате этого извлечения до 004604.
Центральный процессор распознает, что инструкция должна быть индексиро-
вана и, следовательно, для завершения вычисления адреса ЭЯ ЦП должен
получить этот индекс. Он получает индекс (001442) посредством извлечения,
увеличивая тем самым с (PC) на 2, так что теперь с (PC) = 004606. Обратите
внимание, что увеличение с (PC) вызвано извлечением, а не каким-либо авто-
увеличением, связанным с самой операцией. Теперь ЦП располагает всей ин-
формацией, необходимой для вычисления адреса ЭЯ. Он прибавляет индекс
(001442) к содержимому PC (004606) и получает 006250, что и является ад-
ресом ЭЯ. Содержимое ячейки 006250 пересылается в регистр R3.
Прежде чем комментировать этот режим адресации, рассмотрим несколь-
ко более усложненный пример:
004602 016767
004602 001442
004606 175454
004610 -------
MOV 1442(PC), 175454 (PC)
(индекс 1442)
(индекс 175454)
Здесь как ЭЯ источника, так и ЭЯ приемника определяются путем индек-
сирования PC, и, хотя ситуация вполне понятна, для вычисления адресов ЭЯ
источника и ЭЯ приемника требуется определенное внимание. Вычисление ад-
реса ЭЯ источника не отличается От того, как это делалось в предыдущем
примере, — это 006250. Что касается приемника, то мы должны знать, что к
моменту, когда ЦП готов начать вычисление ЭЯ приемника, с (PC) = 004606.
Опять ЦП распознает индексный режим приемника (с обращением к PC) и
извлекает индекс, увеличивая тем самым с (PC) до 004610. Извлеченный ин-
декс равен 175454, что после прибавления к текущему значению с (PC)
(004610) дает 002264. (Почему?) Таким образом, действие этой трехслов-
ной инструкции состоит в пересылке содержимого ячейки памяти, адрес ко-
торой есть 006250, а ячейку памяти с адресом 002264.
Теперь нам известны три способа задания адреса ячейки памяти в инструк-
ции. Во-первых, мы можем поместить адрес ячейки в какой-то регистр, на-
127
пример в Rl, и затем ссылаться на (R1) или (Rl)+. Во-вторых (и это наибо-
лее просто), мы можем й'спользовать режим адресации 3 с PC, указывая сим-
волически @#ADDR. Наконец, как только что было показано, мы можем
указать адрес в виде индекса, т. е. смещения, которое должно прибавляться
к текущему содержимому программного счетчика. Эта последняя схема с
практической точки зрения работает сама против себя. Чтобы определить ин-
декс, мы должны в общем случае знать, где в оперативной памяти располага-
ется сама инструкция и каков адрес ЭЯ, даже если это ссылка вперед, и по-
сле этого мы должны правильно вычислить индекс. Очевидно, что это слиш-
ком большая нагрузка на программиста.
Однако прежде, чем отвергнуть этот режим как непрактичный, заметим,
что после первого прохода ассемблеру известно значение адреса ЭЯ (даже ес-
ли он был дал в символьном виде), известен адрес, начиная с которого ас-
семблируется инструкция с индексным режимом адресации с PC и, следова-
тельно, ассемблер легко может вычислить правильный индекс (смещение
PC). Таким образом, нам необходимо только придумать какую-то нотацию
для информирования ассемблера о нашем желании, чтобы адрес ЭЯ опреде-
лялся с помощью режима адресации 6 с PC. Тогда ассемблер сгенерирует
индексированную инструкцию, вычислит соответствующий индекс (смеще-
ние PC) и вставит этот индекс в слово, следующее за операционным словом.
В последней главе мы уже изобрели такую нотацию — для индикации этого
режима адресации и использовали адрес ЭЯ без таких обозначений, как @,
или @#, или #.
Простой пример проиллюстрирует этот тип адресации. Фактически мы уже
использовали этот вид инструкции, хотя в то время читатель мог и не знать
об этом (см. листинг программы на рис. 6.7.1, особенно строку 13).
000030 010567 MOV R5, ANSWER
000032 000036
000034 -------
Из таблицы символов мы видим (а к концу первого прохода ассемблер зна-
ет), что ANSWER = 000072. Обратите внимание, что была генерирована кон-
струкция режима 6 (010567) и что индекс (000036) совершенно правиль-
ный. К моменту, когда инструкция извлечена из 000030, PC содержит 000032.
Дополнительное извлечение, необходимое для получения индекса 000036,
увеличит с (PC) до 000034. Таким образом, после прибавления содержимого
PC к индексу имеем 000036 + 000034 = 000072 — адрес ANSWER. Этот режим
адресации с PC, использованный до сих пор наиболее часто, называется режи-
мом относительной адресации с программным счетчиком, поскольку адреса
определяются относительно текущего значения программного счетчика пу-
тем смещения его на соответствующее значение индекса.
Теперь кажется, что два разных режима адресации с PC — 3 и 6 - дают один
и тот же эффект. Каково различие между INC BUFFER и INC @#BUFFER?
С точки зрения программиста эти инструкции представляются идентичными
в том смысле, что обе увеличивают слово с (символьным) адресом BUFFER.
Но, как нам известно, ассемблер обрабатывает эти две инструкции совсем
по-разному. В первом случае ассемблер должен вычислить соответствующий
индекс и вставить его в слово, следующее за инструкцией увеличения с режи-
мом 6, и этот индекс будет использоваться как смещение текущего значения
128
PC к BUFFER. Во втором случае ассемблеру необходимо только сгенериро-
J вать инструкцию режима адресации 3 с PC и затем вставить число BUFFER в
I слово, следующее за операцией. Таким образом, пока рассматриваются дей-
ствия программиста (или даже выполнение программы), использование того
или иного типа инструкции может решаться с помощью жребия. И так будет
до тех пор, пока мы досконально не обсудим перемещение программ и пози-
ционно-независимый код, когда сможем объяснить преимущество одного из
| этих режимов перед другим. Пока же читатель должен знать, что они оба да-
5 ют один и тот же эффект, и может использовать произвольно любой из них.
. з) Адресация с программным счетчиком, режим 7 (111).
Нет ничего удивительного, что структура инструкции с режимом 7 с PC
такая же, как и с режимом 6, за исключением того, что адрес, определяемый
смещением PC на величину соответствующего индекса, является адресом не
ЭЯ, но ячейки памяти, содержимое которой есть адрес ЭЯ. Простой пример
прояснит это:
004602 005477 NEG @13422 (PC)
004604 013422 Индекс 13422
004606 --------
Адрес ЭЯ вычисляется следующим образом: когда извлекается операцион-
ное слово (005477), с (PC) увеличивается до 004604. Затем выполняется дру-
гое извлечение, и с (PC) тем самым увеличивается до 004606. Индекс, полу-
ченный при этом втором извлечении, прибавляется к текущему значению PC,
что дает 013422 + 004606 = 020230. Содержимое ячейки памяти с этим адре-
сом (020230) и есть адрес ЭЯ. Очевидно, что индекс, чтобы иметь какой-то
смысл, должен быть четным, так как при прибавлении его к с (PC), которое
всегда четное, результат должен быть адресом слова.
Чтобы показать ассемблеру, что мы хотим использовать конструкцию ре-
жима адресации 7 с PC, мы указываем (числовой или символьный) адрес,
поместив перед ним символ @, например NEG @ 20230 или TSTB @ADDR.
Этот режим называется режимом косвенной относительной адресации с про-
граммным счетчиком. Чтобы проиллюстрировать вычисление адреса ЭЯ в
этом режиме, приводим сегмент программы (рис. 7.6.1), но не полную про-
грамму. В последующих примерах программ мы иногда будем использовать
этот режим, но он не столь часто используется, как режим 6.
003604 003606 003610 012302 020446 026330 VECTOR: .WORD .WORD .WORD ADDR1 ADDR2 ADDR3
006332 006334 017703 75250 MOV 0VECT0R+2>R3
006336
012302 000072 ADDR1: .WORD 72
020446 173005 ADDR2: ‘word 173005
Рис. 7.6.1 026330 002406 ADDR3: .WORD 2406
5 Зак. 2212
Нас, конечно, интересует инструкция пересылки в 006332 - MOV ©VEC-
TOR + 2, R3. Какое число пересыпается в регистр 3 с помощью этой инструк-
ции? Во-первых, обратите внимание, что ассемблер предпринял некоторые
ожидаемые действия. Что касается самой операции в ячейке 006332, то она
правильно сформирована как операция с режимом адресации 7, использую-
щая PC, - с кодом 017703. Затем в ячейке 006334 ассемблер поместил ин-
декс 175250. Давайте проверим правильность этого индекса. К моменту, ко-
гда индекс извлечен, содержимое PC равно 006336. При сложении этого чис-
ла с индексом получаем 175250 + 006336 == 003606, что фактически и есть
VECTOR + 2 (003604 + 2 = 003606). (Обратите внимание, что ассемблер спра-
вился со ссылкой на VECTOR + 2 и это потребовало от него лишь несложных
арифметических действий.)
Теперь заметим, что число 003606 не является адресом ЭЯ, т. е. содержи-
мое ячейки 003606 не пересылается в R3, а вычисление отодвигается на один,
дополнительный шаг; адресом ЭЯ является содержимое ячейки 003606, а
именно 020446. Таким образом, в R3 пересылается содержимое ячейки памя-
ти 020446. Следовательно, в конце концов мы видим, что в R3 пересылается
число 173005.
Так же, как и для адресации с регистрами общего назначения, мы свели
режимы адресации с программным счетчиком в табл. 7.6.2, но в нее мы вклю-
чили только те режимы, которые представляются особенно полезными.
Таблица 7.6.2
(Полезные) режимы адресации с PC в PDP-11
Режим Символ Название Эффективная ячейка (ЭЯ)
2 # п Непосредственный Операнд п следует за операционным словом
3 @#А Абсолютный Адрес А эффективной ячейки следует за опе- рационным словом
6 А Относительный Адрес А эффективной ячейки = операцион- ный адрес + 4 + индекс (индекс следует за операционным словом)
7 @А Косвенный относительный А является адресом адреса ЭЯ, где А = опе- рационный адрес + 4 + индекс (индекс следу- ет за операционным словом)
Описания ЭЯ в этой таблице могут оказаться несколько обманчивыми;
они включены здесь больше как руководство, нежели как абсолютные фор-
мулы. Например, в инструкции MOV #3, @А, которая будет ассембли^ювана
как
012777
000003
(смещение к А)
правильно то, что число 3 при непосредственном режиме адресации помещено
в слове, следующем за операционным словом. Но индекс — смещение к А —
находится во втором слове, следующем за операционным словом. Более то-
го, адрес А вычисляется так, что к операционному адресу прибавляется 6
130
плюс индекс. Читатель, твердо усвоивший режимы адресации, не встретит
трудностей из-за этих несоответствий и, вероятно, будет больше полагаться
на понимание, нежели на формулы.
7.7. НЕКОТОРЫЕ ИСКЛЮЧИТЕЛЬНЫЕ СЛУЧАИ
В последних трех разделах мы изучили большой класс инструкций - услов-
ные ветвления и такие инструкции, в которых адрес источника, адрес прием-
ника или оба определяются тремя битами режима и тремя битами регистра.
Мы можем также из-за их простоты рассмотреть такие инструкции, которые
не имеют операндов. Осталось, однако, несколько операций, которые не по-
падают ни в одну из этих категорий. Некоторые из них — JSR, RTS, TRAP и
г. п. — мы обсудим позднее, когда это будет наиболее естественно. С другими
же мы вообще не будем иметь дело и оставляем читателю изучение их форма-
тов, приведенных в приложении А. В этом разделе мы опишем несколько ин-
струкций, представляющих особый интерес..
Интересной и очень полезной инструкцией является SOB (Subtract-One-
and-Branch-back — вычесть-единицу-и-перейти-назад). Формат этой однослов-
ной инструкции такой:
О 111 ИГ ггг ххх ххх
или в восьмеричном виде:
Q77RXX
Здесь R (или ггг) заменяет трехбитовое обозначение регистра (от ООО до 110;
вскоре станет очевидным, что R7 не может быть подходящим регистром для
пой инструкции), аXX (или ххх ххх) заменяет пословное смещение к PC.
Мнемоника ассемблера для этой инструкции имеет вид: SOB Rn, ADDR.
В следующем примере эта инструкция будет использована для усовершен-
ствования одной из наших более ранних программ.
Инструкция SOB работает следующим образом. Содержимое заданного ре-
гистра уменьшается на 1; если результат не нулевой, то происходит ветвле-
ние к адресу ADDR. Если после уменьшения содержимое регистра становит-
ся нулевым, то ветвления не происходит, а управление просто передается сле-
дующей инструкции. Таким образом, SOB является инструкцией для .управ-
ления циклом — мы можем загрузить в регистр счетчик (число повторений
цикла), сделать так, чтобы ADDR был символьным адресом первой инструк-
ции цикла, а затем закончить цикл инструкцией SOB Rn, ADDR. Цикл будет
выполнен, содержимое регистра уменьшено, управление вернется назад к
ADDR, цикл выполнится снова и т. д. до тех пор, пока в конце концов содер-
жимое регистра не уменьшится до 0. Тогда уже ветвление не произойдет, т. е.
мы выйдем из цикла. Обратите внимание, что это не тот случай, когда ветвле-
ние происходит, пока регистр-счетчик положительный-, ветвление имеет ме-
сто, пока регистр не нулевой. Заметим также, что уменьшение содержимого
регистра происходит до проверки на 0.
Как можно ожидать, для смещения текущего значения PC, т. е. для дости-
жения ветвления, используется шестибитовое смещение. Как это было и в
случае инструкций условного ветвления, смещение берется как пословное,
и по той же причине смещение PC всегда должно быть четным. Но в противо-
положность условным ветвлениям самый старший из этих шести битов мы не
5*
131
используем в качестве знакового. Вместо этого ЦП полагает, что данное по-
словное смещение должно порождать ветвление в обратном направлении е.
отрицательное смещение PC. Причина для выбора такой конструкции — дво-
якая. Во-первых,'инструкция, управляющая циклом, обычно располагается в
конце цикла, а не в его начале, и, таким образом, требуется выполнять вет-
вление назад. Во-вторых, если бы первый из шести битов использовался в ка-
честве знакового, оставшиеся пять битов могли бы удержать только числа не
больше 3110, а это значительно ограничивало бы длину циклов, которыми
можно управлять с помощью этой инструкции. А так мы ограничены уже
числом 6310 слов.
Действие инструкции SOB описывается следующим образом:
Rn c(Rn) - 1
PC *- с (PC) — 2 х (пословное смещение), если c(Rn) =/= О
(Вспомним, что символ *- обозначает ’’присваивается значение...”.)
За примером инструкции SOB обратимся опять к программе ha рис. 7.5.3.
Вспомним, что регистр R0 использовался в качестве счетчика и ему было при-
своено значение 4, а цикл управлялся путем уменьшения c(R0) и затем про-
верки его на 0. В частности, строки 9 и 10 этой программы были такими:
9 000022 005300 NEXT: DEC R0
10 000024 001372 BNE LOOP
Эти две инструкции могут быть заменены одной:
9 000022 077005 NEXT: SOB R0, LOOP
Как вторую интересную и порою полезную инструкцию рассмотрим ИС-
КЛЮЧАЮЩЕЕ ИЛИ, для которой используется мнемоника ассемблера XOR.
Ее операционный формат таков:
0 111 100 rrr ddd ddd
или в восьмеричном представлении:
014RDD
Здесь опять R указывает некоторый регистр, тогда kzkDD отсылает к обыч-
ному приемнику, состоящему из трех битов режима и трех битов регистра.
Инструкция работает так: содержимое приемника инструкции заменяется ре-
зультатом выполнения побитовой операции ИСКЛЮЧАЮЩЕЕ ИЛИ над задан-
ными регистром и приемником. Для примера предположим, что c(R4) =
= 104223, c(Rl) = 002644 и с(002644) = 036435. Тогда с помощью инструк-
ции XOR R4, (R1) содержимое ячейки 002644 будет заменено на 132616, т. е.
на значение, получаемое в результате операции ИСКЛЮЧАЮЩЕЕ ИЛИ над
104223 и 036435.
Замечание. На некоторых моделях ЭВМ PDP-11 инструкции XOR и SOB не
реализованы. В случае SOB это не приводит к серьезным последствиям, по-
скольку SOB Rn, ADDR всегда может быть заменена последовательностью
двух инструкций
DEC Rn
BNE ADDR
132
Наконец, остались еще инструкции умножения (MUL) и деления (DIV),
причем здесь мы рассмотрим умножение, оставив читателю самостоятельно
разобраться в деталях инструкции DIV, которую он найдет в приложении А.
Как арифметическая операция умножение обладает той особенностью, что в
общем случае произведение двух 16-битовых чисел дает в результате число,
состоящее из 32 бит. Поскольку размер слова ЭВМ PDP-11 равен 16, это по-
рождает некоторые проблемы, с которыми нам приходится иметь дело. Ре-
шение довольно интересное — ЭВМ PDP-11 ’’сдвигает вместе” два 16-битовых
регистра для образования 32-битового суперслова, в которое помещается
произведение. Как будет видно из последующей нотации, для этого не могут
использоваться просто два любых регистра.
Нотацией Rn V 1 будем обозначать регистр, номер которого образуется в
результате логической операции ИЛИ над числами п и 1. Таким образом,
например, R2 V 1 = R3, поскольку 2 V 1 =3, тогда как R3 V 1 = R3, посколь-
ку 3 V 1 = 3. Располагая этой нотацией, мы можем теперь описать действие
операции MUL.
Формат оператора умножения такой:
О 111 000 rrr sss sss == 070 RS S
Здесь/? представляет регистр, а55 (нормально сформированный) источник,
состоящий из трех битов режима и трех битов регистра. Действие инструкции
состоит в умножении содержимого заданного регистра на содержимое источ-
ника и в помещении результирующего 32-битового произведения (со знаком)
в 32-битовую регистровую комбинацию, состоящую из R и RV 1- Для кон-
кретного примера предположим, что c(R2) - 002605 и c(R5) = 030024. То-
гда инструкция MUL R5, R2, код которой есть 070205, поместит произведе-
ние этих двух чисел, а именно 00102257144, в R2 и R3 (R3 = R2 V 1) - В част-
ности, после выполнения получим c(R2) = 000411 и c(R3) =057144. (Поче-
му?) Если произведение двух чисел отрицательное, то самый старший бит,
т. е. бит 15 первого из двух регистров, будет установлен в 1.
Что бы случилось в приведенном примере, если бы заданным регистром
был не R2, a R3, т. е. если бы было c(R5) = 030024 и c(R3) = 002605, и мы
выполнили бы операцию MUL R5.R3? Значение этого изменения состоит в
том, что R3 V 1 = R3. То есть очевидно, что нет второго регистра для удержа-
ния 32-битового произведения. И поскольку такого второго регистра нет,
младшие 16 бит произведения были бы помещены в R3, а старшие 16 бит бы-
ли бы потеряны. По этому поводу следуеь дать два пояснения. Во-первых, ес-
ли какие-то биты при этом процессе теряются, то такая ситуация может быть-
обнаружена, поскольку в этом случае будет установлен индикатор переноса.
Поэтому вслед за операцией умножения мы можем поставить инструкцию
типа BCS ERROR для передачи управления к инструкциям, которые способ-
ны справиться с потерей битов. Во-вторых, может оказаться, что мы распола-
гаем достаточной информацией, чтобы утверждать, что произведение поме-
стится в 16 бит; в этом случае можно безопасно использовать под него ре-
гистр с нечетным номером.
Замечание. На некоторых моделях ЭВМ PDP-11 (особенно на ранних и
очень малых машинах) аппаратура для реализации инструкций MUL и DIV
отсутствует. На этих машинах произведения и частные должны вычисляться
133
программным способом, например с помощью последовательных сложении и
вычитаний соответственно.
7.8. НАБОР ИНСТРУКЦИЙ ЭВМ PDP-l 1
Теперь мы можем дать обзор типов операции ЭВМ PDP-11. Вообще говоря,
к ним относятся инструкции без операндов, с одним операндом, двумя опе-
рандами, инструкции ветвления и несколько инструкций, представляющих
исключительные случаи. Ниже они разделены на категории, причем в каждом
случае мы даем мнемоники для операций, попадающих в каждую группу, а
полное описание их может быть найдено в приложении А. Некоторые из этих
мнемоник нам уже знакомы, большинство других — еще нет, хотя большую
часть из них мы будем использовать в оставшихся главах. Сюда не включены
несколько инструкций специального назначения, которые реализованы не на
всех моделях ЭВМ PDP-11.
Ниже используются следующие битовые нотации:
с = код условия
d = приемник
п = число
о = операционный код
г = регистр
S = источник
х = пословное смещение
— = не используется
Тип: Без операндов
Формат: О ООО 000 000 000 000 ИЛИ О ООО 000 о—
Операторы: ВРТ EMT HALT ЮТ NOP RESET RTI RTT TRAP WAIT
Тип: Однооперандные
' Формат: о ооо ооо ооо ddd ddd
Операторы: ADC ADCB ASL ASLB ASR ASRB CLR CLRB COM COMB DEC DECB INC INCB IMP NEG NEGB ROL ROLB ROR RORB SBC SBCB SWAB SXT TST TSTB
Тип: Двухоперандные
Формат: о ооо sss sss ddd ddd
Операторы: ADD BIC BICB BIS BISB BIT BITB CMP CMPB MOV MOVB SUB
Тип: Ветвления
Формат: О ООО 000 ОХХ XXX XXX
Операторы: ВСС BCS BEQ BGT BHI BHIS BLE BLO BLOS BLT BMI BNE BPL BR BVC BVS
Тип: Регистр и источник или регистр и приемник
Формат: о ооо ооо rrr sss sss или о ооо ооо rrr ddd ddd
Операторы: ASH ASHC DIV JSR MUL XOR
Тип: Для кодов условий
Формат: О ООО 000 poo оос ссс
Операторы: ССС CLc SCC SEc
134
Тип: Разные
Операторы: MARK о ооо ооо ооо ппп ппп
RTS о ооо ооо ооо ооо rrr
SOB о ооо ооо rrr ххх ххх
SPL О ООО 000 000 000 ппп
7.9. НЕСКОЛЬКО ЗАКЛЮЧИТЕЛЬНЫХ ЗАМЕЧАНИЙ
Итак, мы изучили различные режимы адресации, доступные на ЭВМ PDP-11,
причем делали это с определенной степенью детализации, которая порою мог-
ла показаться читателю чрезмерной и скучной. Но поступали мы так намерен-
но по двум причинам. Во-первых, полное понимание этих режимов очень су-
щественно для хорошего и эффективного программирования на машинном
уровне, а достижение этого является, конечно, одной из целей нашей книги.
Что-либо меньшее, чем твердое знание этих фундаментальных основ, может
вести только к плохо структурированным программам, скрытым програм-
мным ошибкам и смутному пониманию аппаратных возможностей. Во-вто-
рых, вспомним, что наше намерение состояло не в том, чтобы провести исчер-
пывающее изучение именно ЭВМ PDP-11, но в том, чтобы достичь понимания
возможностей любой современной ЭВМ. Твердое знание того, как PDP-11
осуществляет доступ к памяти и регистрам с помощью различных регистро-
вых режимов позволит читателю писать программы на языке ассемблера для
других ЭВМ с минимальной потребностью в переобучении.
Если в этой главе мы дали представление о том, что ЭВМ PDP-11 распола-
гает очень ’’богатым” набором инструкций (большое число полезных ин-
струкций с многочисленными режимами, в которых они могут использовать-
ся), то это было сделано намеренно. Варьируя биты режима и регистра в ис-
точнике и приемнике, мы фактически можем обращаться с одной только ин-
струкцией MOV 4096-ю различными способами, хотя и не все из них суще-
ственно различаются и не все представляются особенно полезными. Но такое
богатство набора инструкций не является специфической особенностью ЭВМ
PDP-11. Большинство современных ЭВМ способны на это, а некоторые из
машин большего размера обладают существенно более развитыми наборами
инструкций. Даже вездесущие микро-ЭВМ, хотя и располагающие несколько
более грубыми относительно стандартов ЭВМ PDP-11 наборами инструкций,
обладают тем не менее всеми этими возможностями. Поэтому читатель, ясно
понимающий, что такое машинная инструкция и что ЦП делает с ней, не встре-
тит существенных проблем на пути к успешному программированию на этих
популярных машинах.
И, наконец, вспомним, что в гл. 4 нам показалось наиболее желательным
использовать для 16-битовых чисел шестнадцатеричное представление, тогда
как восьмеричное представление казалось менее желательным, особенно из-
за того, что границы восьмибитовых байтов не совпадали с границами вось-
меричных цифр. Но теперь читателю должно быть понятно, что с шестнадца-
теричным представлением было бы обращаться весьма трудно. Рассмотрим
в качестве примера 16-битовую конфигурацию, представляющую инструк-
цию BISB (R4),@-(R2):
1101001100101010
135
В восьмеричном виде она была бы записана как 151452, что можно разло-
жить на такие компоненты:
15 1 4 5 2
BISB реж. per. реж. per.
14 5 2
В шестнадцатеричном виде соответствующее представление имело бы вид
D32A, и мало надежды на то, что из этого числа, представленного по основа-
нию 16, можно извлечь много информации. Все это происходит из-за спосо-
ба, каким структурируется аппаратура. Поэтому для ЭВМ PDP-11 наиболее
естественным оказывается восьмеричное представление, тогда как для других
машин существенно более информативным может быть шестнадцатеричное.
7.10. УПРАЖНЕНИЯ
7.5.1. Предположим, что c(R2) = 104505 и c(R3) = 026101. Каким будет содержи-
мое каждого из этих регистров после выполнения инструкции MOVB R2,R3?KaK будет
отличаться результат, если с (R2) = 104305? (Действие операции МОТЬ с приемником-
регистром, т. е. в режиме 0, заключает в себе небольшую хитрость. Здесь будет уместно
изучить описание этой инструкции по приложению А.)
7.5.2. Предположим, что непосредственно перед выполнением каждой из инструкций
MOV (01SSDD) или MOVB (11SSDD) мы имеем:
c(R2) = 021314
c(R4) = 002016 с(001026) = 103076 с (021312) = 063022
с(002014) = 001026 с(021314) = 104202
с(002016) = 010302 с(021316) = 042104
с (002020) = 030300 с(ОЗОЗОО) - 000106
с (010300) = 017772 с(042104) = 101114
с (010302) = 000000 с(063022) = 060662
с(010304) = 100205 с(104202) = 013235
В каждом случае запишите мнемонический вид инструкции, определите ЭЯ источника и
приемника и укажите, содержимое каких регистров или ячеек памяти (или то и другое)
изменяются с помощью инструкции, а также дайте их новые значения.
а) 010204 з) 111402 п) 014204 ф) 017274
б) 010402 и) 012204 р) 014412 000002
в) 110204 к) 112204 с) 113422 177776
г) 110402 л) 011214 т) 016204 х) 016434
д) 011204 м) 011224 000002 000002
е) 011402 н) 013214 у) 116462 ц) 015254
ж) 111204 о) 012244 177777
000001
7.5.3. На с. 117 для иллюстрации индексного режима мы рассматривали инструкцию
COMB -12(R5), где с(R5) было равно 026301.
Как она отличается от инструкции COMB 26301(R5) , где с (R5) = 177766(—12) ?
7.5.4. Модифицируйте программу на рис. 7.5.1 для нахождения числа байтов, превос-
ходящих по значению следующий за ним байт.
7.5.5. Покажите, что в программе на рис. 7.5.1 использование R1 в качестве счетчика
стало бы не нужным, если бы строка 7 была изменена с DEC R1 на СМР R3, #VECTOR +
+ 14.
136
7.5.6. На рис. 7.5.3 представлен листинг модифицированной программы для нахожде-
ния наибольшего из пяти чисел. Модифицируйте эту программу дальше таким образом,
чтобы содержимое R2 подвергалось уже не автоматическому увеличению, а автомати-
ческому уменьшению.
7.5.7. Каким будет действие инструкции ADD (Rl), (R1)? Как оно отличается от дей-
ствия ADD (R1)+, -(Rl)? Действия ADD (Rl), (Rl)+? Действия ASL (R1) или ASL
(Rl)+?
7.5.8. Как можно переслать старший байт регистра R2 в старший байт регистра R3,
оставляя младший байт R3 без изменения? (Это отнюдь не тривиальное дело.)
7.6.1. Положим, что первое слово каждой из следующих инструкций MOV (01SSDD)
или MOVB (11SSDD) загружается в ячейку 014626. Для каждого случая найдите ЭЯ ис-
точника и приемника.
а) 012715 б) 016715 003212 в) 016767 003212 г) 116767 002313
002313
д) 010167 е) 013767 002314 003212
172304 024606 ж) 010377 з) 013727
003146 024030 024606
003146
7.6.2. Предположим, что каждая из следующих инструкций MOV или MOVB загружа-
ется, начиная с ячейки 010224, и что символьные адреса АВС и XYZ имеют следующие
значения: АВС = 002776, XYZ = 017242.
Постройте (восьмеричный) код для каждой инструкции
a) MOV R0, XYZ
в) MOV @# ABC, XYZ
д) MOV ABC, XYZ
ж) MOV @ XYZ, (R3)+
и) MOVB ABC - 7, -(R2)
л) MOV # XYZ, ABC
6) MOV @#ABC, R2
r) MOV #6, ABC
e) MOV XYZ, ABC
3) MOVB @ ABC, XYZ+ 7
к) MOV #ABC, XYZ
7.6.3. В упражнении 6.3.3 мы просили сказать, что означает MOV R0, #206 (если эта
инструкция вообще что-либо означает). Хотя в такой инструкции обнаружить смысл
трудно, ассемблер тем не менее генерирует для нее следующий код:
010027
000206
Объясните, что произойдет, если будет выполнена эта пара оператор—операнд. Анало-
гично определите, какой код генерирует ассемблер для CLR #4 и INC #622, и объясни-
те, что произойдет при выполнении каждой из этих инструкций.
7.6.4. Для увеличения старшего байта слова с символьным адресом DATA нам необ-
ходимо только написать INСВ DATA + 1. Но как можно увеличить старший байт регист-
ра 4? В частности, покажите, что INCB R4 + 1 не будет правильно обрабатываться ас-
семблером.
7.6.5. Какое число будет пересылаться в R2 при выполнении инструкции MOV — (PC),
R2, приведенной на с. 126?
7.6.6. Каково различие между двумя инструкциями СМР (R3)+, (R3) и СМР (R3),
2(R3)?
7.6.7. Следующая мнемоническая программа просто складывает положительные це-
лые числа от 1 до 10 и помещает результат (5510) в ячейку памяти с меткой ANSWER.
Полагая, что программа должна быть ассемблирована, начиная с ячейки 000000, по-
стройте ассемблированный машинный код, т. е. сыграйте роль ассемблера или нашего
служащего. Должны ли быть какие-либо слова в машинной программе помечены апо-
137
строфом для индикации того, что они чувствительны к перемещению программы? Ка-
кой будет таблица символов?
ANSWER: .BLKW 1
BEGIN: CLR @# ANSWER
CLR R4
LOOP: INC R4
ADD R4, ANSWER
CMP R4, #12
BNE HALT LOOP
.END BEGIN
(Полученный в результате код может быть проверен с помощью прогона исходной про-
граммы через ассемблер ЭВМ PDP-11.)
7.7.1. Инструкция SOB R0.LOOP на с. 132 имела машинный код 077005. Почему? В
частности, откуда взялись шесть битов 05?
7.7.2. Предположим, что инструкция SOB загружается в ячейку 014626, регистр R2
является управляющим для этой инструкции, а целевой адрес есть 014576. Постройте
инструкцию SOB в восьмеричном представлении.
7,7.3. Рассмотрите следующую программу, содержащую инструкцию SOB (числа
представляют адреса, в которые загружаются инструкции) :
002606 LOOP:----------------
003012 SOB R5, LOOP
Что неправильно в этой конструкции? Укажите хотя бы два способа, какими можно вне-
сти исправления.
7.7.4. На с. 131 мы утверждали, что R7 не может быть подходящим регистром для ис-
пользования в инструкции SOB. Почему?
7.7.5. Если начальное значение Rn равно пулю и Rn используется в инструкции SOB
для управления числом повторений цикла, то выйдет ли когда-нибудь из цикла эта про-
грамма? Почему да или почему нет?
7.7.6. Каково действие приведенной ниже последовательности инструкций?
XOR R1,R3
XOR R3,R1
XOR R1.R3
{Указание. Чтобы получить представление о том, что здесь происходит, предположите,
что R1 и R3 удерживают, например, только четырехбитовые слова, и посмотрите, что
произойдет, когда для этих регистров выбраны какие-то определенные значения, напри-
мер с (R1) = 0110 и с (R3) = И 01. Тогда можно довольно легко догадаться, что делают
эти инструкции. Теперь попытайтесь распространить эту догадку на общий случай - для
любой битовой конфигурации и для любого размера слова.)
7.7.7. Очень часто вычислительные машины используются для сортировки числовых
данных. Напишите программу для упорядочения набора чисел в возрастающем порядке.
Для решения этой задачи существует много методов; один из наиболее легко реализуе-
мых (но вряд ли самый эффективный) - это так называемая обменная (или пузырь-
ковая) сортировка.
138
э- * @ 7 19 10 -5
-2 CD CD 19 10 -5
—2 1 © CD 10 -5
—2 1 7 (D ' ® -5
-2 1 7 10 ® * -* СЕ
-2 1 7 10 -5 19
Рис. 7.10.1
Идея весьма проста. Мы сравниваем первые два числа из заданного набора. Если они
расположены в правильном порядке (первое меньше или равно второму), то мы пере-
ходим к сравнению второго и третьего чисел. Если же они расположены в неправильном
порядке, то мы обмениваем их, прежде чем перейти к следующей паре. К моменту, ког-
да мы сравнили (и, возможно, обменяли) все последовательные пары, наибольшее из
всех чисел будет перемещено на самую последнюю позицию. Таким образом, этот пер-
вый проход производит частотную сортировку (для полной сортировки потребуются
дополнительные проходы). Пример пояснит эту процедуру.
Предположим, что заданы такие числа (показанные в десятичном виде):
1 -2 7 19 10 —5
расположенные в указанном здесь порядке (слева направо). Первый проход представ-
лен на рис. 7.10.1, где кружками показаны сравниваемые пары, а двойные стрелки
указывают на необходимость обмена. Хотя были сделаны некоторые улучшения, шесть
чисел еще остаются неотсортированными. Обратите внимание, что наибольшее число
(19) переместилось с самого начала в самый конец (объясните почему это всегда проис-
ходит) .
Теперь мы повторяем процесс над урезанным набором:
-2 1 7 10 -5
поскольку рассматривать самое большое число (19) теперь нет нужды. После пяти та-
ких проходов и урезания каждый раз набора чисел первоначальный набор будет упоря-
дочен.
Здесь есть два вложенных цикла - один для управления отдельными проходами, дру-
гой - для управления числом проходов. Каждый цикл должен управляться инструк-
цией SOB. Проверьте эту программу на восьмеричных числах (заданных в указанном
порядке):
24, 17726, 0, 177726, 24, 1, 0, 101101, 12345, 120220, 20
7.7.8. Как следует модифицировать программу из упражнения 7.7.7 для упорядоче-
ния набора чисел в порядке уменьшения*.
7.7.9. На рис. 7.10:2 показан листинг программы, которая, хотя и не делает ничего по-
лезного, тем не менее может выполняться. Определите значение, которое при выполне-
нии программы будет находиться в R4 к моменту, когда встретится инструкция HALT
в строке 10.
7.7.10. В Тексте мы утверждали, что инструкция SOB Rn.ADDR может быть замене-
на на
DEC Rn
BNE ADDR
но все же эти две конструкции не иденгичны. В чем состоит различие?
139
1 001000 010767 000006 START: MOV PC,SKIP
2 001004 105267 000004 INCB SKIP+2
3 001010 005004 CLR R4
4 001012 00040? SKIP: BR CONT
5 001014 005203 INC R3
6 001016 000775 BR SKIP
7 001020 152767 000014 000003 CONTs BISB 614,1031
8 001026 000304 .HORD 000304
9 001030 000304 .HORD 000304
10 001032 000000 HALT
И
1? 001000 .END START
Рис. 7.10.2
7.7.11. Полагая, что инструкция XOR не реализована аппаратно, напишите последо-
вательность инструкций, которая будет эмулировать, например, инструкцию XOR R5,
R2, т. е. с (R2) будет заменено на с (R5) c(R2), а с (R5) останется без изменений. Яв-
ляется ли эта эмуляция точной? Будут ли, например, коды условий устанавливаться
так, как в случае инструкции XOR, приведенной в приложении А?
7.7.12. 16-битовое слово налагает ограничение на значение чисел со знаком или без
знака, которые могут быть в нем представлены. Чтобы увеличить это значение, рас-
смотрим два последовательных (соседних) слова памяти как представляющие одно
32-битовое ’’суперслово” двойной точности, когда биты 0 - 15 интерпретируются как
слово с меньшим адресом, а биты 16 — 31 располагаются в слове с большим адресом.
Пусть X и Y будут адресами двух таких 32-битовых ’’слов”, чьи старшие и младшие 16-
битовые части имеют соответственно символьные адреса XHIGH и YHIGH, и XLOW и
YLOW. Покажите, как могут эти два 32-битовых слова складываться и вычитаться. Бу-
дут ли индикаторы переноса и переполнения устанавливаться этими арифметическими
операциями двойной точности так, как ожидается, т. е. как в случае сложения и вычи-
тания одинарной точности? Действительно ли необходимо, чтобы 16-битовые слова, об-
разующие одно 32-битовое суперслово, были соседними?
7.7.13. Как можно определить, что 16-битовое слово без знака, например c(R0),
является степенью двойки, т. е. что только один бит установлен в 1? (Результат почти
тривиальный; сама программа — нет.)
7.7.14. Если на конкретной модели ЭВМ PDP-11 умножение не реализовано как аппа-
ратная инструкция, то произведение должно вычисляться с помощью последовательно-
сти программных инструкций. Напишите такую последовательность для эмуляции аппа-
ратной инструкции MUL. В частности, проимитируйте действие инструкций MUL R5, R0
и затем проверьте эмуляцию, установив в R5 значение 000200, ав R0 - 012345. После вы-
полнения инструкции ’’умножения” содержимое различных регистров должно быть таким:
c(R5) = 000200
c(R0) = 000012
c(Rl) = 071200
Работает ли программный сегмент правильно, если множитель или множимое (или оба)
отрицательные? Например, если c(R5) = 177777 и с(R0) = Q12345, или если c(R5) =
— 012345 и с (R0) = \1ПП, то после выполнения должно быть:
c(R0) = 177777
c(Rl) = 165433
Так же ли будут устанавливаться коды условий, как в случае инструкции MUL?
7.7.15. Как и в упражнении 7.7.14, проэмулируйте аппаратную инструкцию DIV. В част-
ности, предположите, что 32-битовое делимое находится в регистровой комбинации
R0/R1, а Делитель расположен в R5. Опять проанализируйте различные комбинации зна-
140
0001 0000 И DATA FCB 17»24»-6»0»-101
0001 18
0002 FA
0003 00
0004 9В
0002 0005 0001 ANSWER RMB 1
0003 1
0004 0006 С6 05 LDA В 45
0005 ОООС 4F CLR A
0006 0009 СЕ 0000 LDX 4 DATA
0007 ОООС АВ 00 LOOP ABD A OrX
0008 ОООЕ 5А DEC В
0009 000F 27 05 BEQ DONE
0010 ООН 7С OO0D INC LOOP+1
ООН 0014 20 F6 BRA LOOP
0012 0016 97 05 DONE STA A ANSWER•
0013 0018 7Е 0000 XIT
0014 I
0015 END
Рис. 7.10.3
ков делителя и делимого; проверьте также коды условий на совместимость с инструк-
цией DIV, описанной в приложении А.
7.9.1. Мы заверили читателя, что, достигнув ясного понимания концепций адресации
памяти, машинных инструкций и режимов адресации, он не встретится с большими труд-
ностями при использовании своих знаний программирования на языке ассемблера ЭВМ
PDP-11 при работе с другими машинами. Чтобы подтвердить это, мы приводим на рис.
7.10.3 листинг исходной программы, ассемблированной для микро-ЭВМ 6800 фирмы
’’Motorola” (архитектура этой машины несколько отличается от архитектуры PDP-11),
и просим читателя определить, что делает эта программа при ее выполнении.
Необходима некоторая предварительная информация. Во-первых, микро-ЭВМ 6800 -
это байтовая машина. То есть группы памяти состоят из восьмибитовых слов, а 16-бито-
вые комбинации используются только для адресации этих байтов и для трех регистров.
Адреса и их содержимое в листинге показаны в шестнадцатеричном представлении, ко-
торое для микро-ЭВМ 6800 подходит больше, чем восьмеричное. Вместо того чтобы
иметь много регистров, микро-ЭВМ 6800 обладает только двумя регистрами общего на-
значения, которые называются аккумуляторами и обозначаются А и В. Кроме того, име-
ется так называемый индексный регистр, обозначаемый X; он имеет длину 16 бит и,
следовательно, может удерживать адрес. Как следует из названия, этот регистр исполь-
зуется так же, как какой-нибудь регистр ЭВМ PDP-11 используется в индексном режи-
ме. Например, если с(Х) = 1856 и c(R3) = 014266 (обратите внимание, что 181?61в =
= 0142668), то ссылка, например, на 6 (R3) есть то же самое, что ссылка на 6, X для
микро-ЭВМ 6800. Далее обратите внимание, что метки не сопровождаются двоеточия-
ми; они отличаются лишь тем, что начинаются в колонке 1 исходного кода. Обозначение-
FCB - это директива ассемблера для формирования байтовой константы, которая анало- .
гична директиве .WORD в случае ЭВМ PDP-11. Аналогично, RMB - это директива для ре-
зервирования байта памяти, подобная .BLKW. И, наконец, LDA — это инструкция для за-
грузки аккумулятора, a LDX - инструкция для загрузки индексного регистра. Мы пре-
доставляем читателю угадать значения других мнемоник1. Выполнение программы на-
чинается с ячейки памяти 0006.
Обратите внимание на инструкцию в строке 10: INC LOOP + 1. Ее действие состоит в
модификации индекса (первоначально установленного в 0) в строке 7. Хотя нам встре-
1 Для читателей, не знакомых с английским языком, дадим подсказку. Мнемоники,
совпадающие со своими собратьями в ЭВМ PDP-11, имеют то же самое значение; BRA -
это инструкция для безусловного перехода к указанному адресу; STA — это инструк-
ция для запоминания содержимого указанного регистра по указанному адресу; XIT —
это макрокод для выхода в операционную систему. - Прим, перев.
141
чались примеры программ, модифицирующих свои собственные инструкции, хорошая
практика программирования состоит в том, чтобы избегать таких инструкций, где толь*
ко возможно. Из-за известных ограничений восьмибитового слова и ограниченного на-
бора инструкций в программировании для микро-ЭВМ самомодифицирующиеся про-
граммы встречаются намного чаще. Серьезных возражений против них нет, если только
такие инструкции хорошо документированы и используются с определенным вниманием.
7.9.2. В гл. 6 мы показали необходимость (возникающую из-за ссылок вперед) того,
чтобы ассемблер делал два прохода по мнемонической исходной программе, фиксируя
на первом проходе значения символьных адресов, чтобы они могли использоваться на
втором проходе. В настоящее время построение такого двухпроходного ассемблера не
представляет проблемы, так как исходные программы обычно располагаются на каком-
либо устройстве памяти большого объема, таком как диск или магнитная лента, и двой-
ной доступ к исходному коду (по одному разу для каждого прохода) - это относитель-
но легкая и быстрая операция. Однако на раннем этапе развития вычислительной техни-
ки такие устройства памяти большого объема не существовали, а большая часть обра-
ботки была ориентирована на перфокарты. Чтобы ассемблировать программу, требова-
лись следующие шаги:
1. Сначала в оперативную память загружалась колода перфокарт (которая могла со-
стоять из 1000 и более карт), содержащая код самого ассемблера.
2. Затем загружались (также на перфокартах) исходные операторы программиста.
3. Ассемблер (находящийся теперь в памяти) выполнял свой первый проход по ко-
лоде перфокарт программиста.
4. Наконец, колода программиста загружалась заново, чтобы ассемблер мог выпол-
нить свой второй проход.
(В действительности ситуация зачастую была намного сложнее и требовалось больше
времени, чем здесь описано.) Поэтому, чтобы уменьшить время, необходимое для ас-
семблирования программы, было потрачено много интеллектуальных усилий на разра-
ботку концепции однопроходного ассемблера, т. е. ассемблера, который может порож-
дать выполняемый код за один проход, даже при наличии ссылок вперед. Как можно
разработать такой ассемблер? (Мы заверяем читателя, что теперь он располагает всей
необходимой информацией, чтобы дать ответ. Мы также подскажем, что ситуяниа здесь
весьма сложная.)
Г Л А В А 8. СТЕКИ И ПОДПРОГРАММЫ
8.1. СТРУКТУРЫ ДАННЫХ
Под структурой данных совершенно неформально мы будем подразуме-
вать лишь любой способ организации данных, удобный с точки зрения про-
граммиста. В некоторых образцах программ в предыдущих главах мы вы-
полняли грубую структуризацию данных, когда настаивали на том, чтобы чи-
сла, подлежащие сложению или максимизации, располагались в последова-
тельных ячейках памяти. Это было необходимо, чтобы мы могли, например,
использовать преимущество автоувеличения при регистровой адресации. Та-
кая организация данных не была абсолютно необходимой, но значительно
упростила конструкцию программ (представьте себе те сложности, которые
появились бы, если бы данные в оперативной памяти были просто рассеяны
в случайном порядке).
На более высоком уровне читатель* возможно, уже накопил определенный
опыт работы с векторами (или линейными стеками) и матрицами (называ-
142
емыми также таблицами или массивами). Реализуя такие структуры, мы по-
именовываем некоторый блок оперативной памяти и затем осуществляем
обращение к конкретному элементу блока по его имени и позиции в блоке,
причем позиция задается одним или несколькими индексами. По этой причи-
. не векторы и матрицы нередко называют индексированными переменными.
Используя такие структуры, программист получает определенную помощь
от конкретной транслирующей программы, которую он использует, - интер-
претатора Бейсика, компилятора Фортрана и т. п. В общем случае вектор или
матрица запоминаются в оперативной памяти в виде блока последовательных
ячеек памяти, а для нахождения адреса нужного элемента переменной прихо-
дится выполнять некоторые арифметические действия. (Ряд предложений по
реализации этих структур на машинном уровне дан в упражнениях.)
Упомянутые выше структуры данных являются примерами статических
структур данных в следующем смысле: если мы первоначально определяем,
что вектор должен состоять из 29 целых чисел, то под эту структуру будут
зарезервированы 29 слов оперативной памяти, а размер вектора (29 слов)
останется фиксированным на протяжений всего процесса выполнения про-
граммы. Конечно, элементы вектора могут и, по всей видимости, будут вре-
мя от времени изменяться, но сам вектор будет статически поддерживаться
как структура из 29 слов. В противоположность этому динамическая струк-
тура — это такая, размер которой может изменяться в процессе выполнения
программы. Одна из таких структур является основной темой настоящей
главы.
Знакомым примером динамической структуры является очередь. Это ли-
нейная структура данных (в том смысле, что для любых двух элементов оче-
реди справедливо утверждение, что один элемент определяется как предше-
ствующий другому), которая может разрастаться и сокращаться динамичес-
ки и элементы которой всегда добавляются к одному ее концу, называемо-
му хвостом очереди, а удаляются с другого конца, называемого головой оче-
реди. Диаграмма структуры очереди показана на рис. 8.1.1.
В качестве повседневного примера рассмотрим очередь покупателей к
кассе универсама. По мере того как покупатели завершают отбор товаров,
они присоединяются к концу ряда людей, направляющихся к кассиру (до-
бавляются в хвост очереди). На другом конце очереди после расчета с касси-
ром они покидают очередь (удаляются из нее). Очередь может быть пустой
(т. е. нет ни одного покупателя, ожидающего у кассы, хотя опыт каждого по-
казывает, что это редко случается) или (в дни с большим наплывом покупа-
телей) теоретически может становиться произвольно длинной. Поскольку
первый элемент очереди является также и первым кандидатом на удаление, то
очередь называют иногда структурой типа ’’первым пришел — первым вышел”.
Менее знакомой, чем очередь, динамической линейной структурой являет-
ся стек, или память магазинного типа. В противоположность очереди, имею-
щей два конца в том смысле, что элементы добавляются к одному концу, а
удаляются с другого, все действия в стеке происходят на одном конце, назы-
ваемой вершиной стека. Таким образом, элементы добавляются к стеку на
его вершине и здесь же удаляются из него. Иэ этого следует, что первый эле-
мент в стеке, доступный для удаления, был последним элементом, добавлен-
ным к стеку, и по этой причине стек называют структурой типа ’’последним
пришел — первым вышел”. Диаграмма стека показана на рис. 8.1.2.
143
Элементы удаляются
из очереди с головы
Элементы добавляются
в стек с вершины
Элементы удаляются из
стека с вершины
Элементы добавляются
в очередь к хвосту
Рис. 8.1.3
Обыденный пример стека найти не так уж легко. Несколько походит на
стек отбой—прикуп в одной карточной игре, в которой карты всегда добав-
ляются (сбрасываются) наверх колоды, а удалена может быть только верх-
няя карта. Однако удалять больше, чем одну карту, не разрешается, и в этом
смысле отбой—прикуп ведет себя не как стек: в конструкции стека как
структуры данных нет ничего, что бы препятствовало удалению из стека
стольких элементов, сколько хочется, при условии, что всегда удаляется по
одному элементу с вершины стека. В качестве действительно характерного
примера рассмотрим пружинный держатель монет, в который монеты про-
талкивают по одной за один раз, а когда монета понадобится, она может быть
вытолкнута с верха держателя. Поскольку первая монета, подлежащая вы-
талкиванию из держателя, была последней, протолкнутой в него, этот пример
является точным аналогом стековой структуры. Мы даже позаимствуем от-
сюда некоторую терминологию и применим ее к нашей структуре данных.
Когда элемент добавляется к вершине стека, будем говорить, что он протал-
кивается в стек; когда элемент удаляется из стека, будем говорить, что он
выталкивается из стека.
Как последний пример рассмотрим стопку тарелок на прилавке (рис.
8.1.3, я). Когда тарелку добавляют к стопке, ее помещают наверх. Аналогич-
но, удаление тарелок происходит сверху, и, таким образом, стопка тарелок
ведет себя в точности так, как интересующая нас структура данных. Конечно,
читатель может возразить, что за один раз можно добавить или удалить не-
сколько тарелок или даже вытащить тарелку из середины стопки, совершив
таким образом действие, нарушающее дозволенную организацию стековой
структуры. Мы можем противостоять этим возражениям, использовав уст-
144
Нижние адреса
002400
002402
002404
002406
002410
002412
002414
Верхние адреса
Рис. 8.2.1
ройство, которым оборудованы многие кухни ресторанов. Вместо того, что-
бы укладывать в стопку на поверхности прилавка, тарелки помещают в про-
резь, оборудованную пружинной подставкой (рис. 8.1.3,б). Когда к стопке
добавляют тарелку, все тарелки в стопке опускаются на толщину одной та-
релки, а когда тарелку удаляют, стопка поднимается на толщину тарелки.
Таким образом, для проталкивания и выталкивания доступной оказывается
только вершина стопки.
8.2. РЕАЛИЗАЦИЯ СТЕКА В ОПЕРАТИВНОЙ ПАМЯТИ
Мы говорим о стеке как о структуре данных, и поэтому в соответствии с
нашим неформальным определением стек обусловливается способом органи-
зации данных в памяти. В этом разделе мы увидим, что довольно легко мож-
но строить стеки в оперативной памяти и вести их, т. е. проталкивать элемен-
ты в стек, выталкивать их из стека и сохранять информацию о том, где нахо-
дится вершина стека.
Рассмотрим регистр (например, R3), содержащий число 2404, которое мы
интерпретируем как адрес 002404. Мы говорим, что R3 указывает на ячейку
памяти с адресом 002404. Посмотрим, каким будет действие следующих
трех инструкций, выполняемых в заданном здесь порядке: v
MOV #17, (R3)+
MOV Rl, (R3)+
MOV ADDR, (R3)+
При выполнении этих инструкций число 17 помещается в ячейку 002404, а
c(R3) увеличивается до 002406. Затем содержимое R1 помещается в ячейку
002406, и c(R3) увеличивается до 002410. Наконец, содержимое ADDR поме-
щается в ячейку 002410, a c(R3) автоматически увеличивается до 002412.
Соответствующая диаграмма показана на рис. 8.2.1.
145
Блок оперативной памяти, начинающийся с ячейки 002404 и простираю-
щийся в сторону верхних адресов (которые, как видно из рисунка, идут, по
крайней мере, до 002412), мы можем рассматривать как стек и считать, что
R3 действует как указатель стека, т. е. R3 содержит адрес вершины стека.
(Здесь мы сталкиваемся с вопросом семантики. Если вершина стека интер-
претируется как ячейка, содержащая последний помещенный в стек элемент,
то из рисунка видно, что R3 указывает на слово, расположенное непосред-
ственно над вершиной стека.) И три приведенные выше инструкции переме-
щения были проталкиваниями в стек.
Если проталкивание в стек равноценно просто инструкции MOV ???, (R3)+,
то легко видеть, что выталкивание из стека это не что иное, как выполне-
ние инструкции MOV — (R3), ???. Например, если задана ситуация, показан-
ная на рис. 8.2.1, и мы хотим'вытолкнуть некоторый фрагмент данных, а
именно c(ADDR), из стека, к примеру, в регистр 4, то нам необходимо лишь
выполнить операцию MOV -(R3), R4. В режиме с предварительным авто-
уменьшением сначала будет уменьшено с (R3) на 2 [так что с (R3) станет рав-
ным 002410] и затем будет переслано содержимое ячейки памяти, адрес ко-
торой находится в R3, в регистр R4. Отсюда видно, что, используя регистр в
качестве указателя стека, мы легко можем вести такой стек, проталкивая
и выталкивая элементы.
Существуют два слегка неприятных момента, с которыми мы довольно
легко справимся. О первом мы уже упоминали, а именно о том, что указа-
тель стека — в нашем случае R3 - указывает не точно на верхний элемент сте-
ка, а на первую неиспользованную ячейку памяти над вершиной стека. Вто-
рой момент касается способа визуализации оперативной памяти. Мы последо-
вательно изображаем диаграммы, в которых нижние адреса памяти располо-
жены сверху диаграммы, а верхние адреса — снизу. Такое представление со-
ответствует листингам ассемблера, где адреса изображаются возрастающими
сверху вниз. При таком соглашении рассмотренный выше стек при проталки-
вании в него элементов растет вниз, тогда как более естественно представле-
ние, что при добавлении новых элементов на вершину стека он растет вверх.
Читателю должно быть понятно, что обе эти трудности являются чисто кон-
цептуальными и поэтому остаются без каких-либо последствий, если рассма-
тривается ведение стека. С другой стороны, обе ’’проблемы” исчезают, если
мы просто организуем стек несколько по-иному.
Предположим, что мы придаем R3 первоначальное значение (адрес) 002412
и затем выполняем следующие инструкции:
MOV #17, —(R3)
MOV R1,-(R3)
MOV ADDR, ~(R3)
Как видно из рис. 8.2.2, стек теперь растет вверх, a R3 действительно указы-
вает на верхний элемент стека. Очевидно, что ведение этого стека выполняет-
ся точно наоборот по сравнению с ведением предыдущего. Чтобы протол-
кнуть элемент в стек, мы выполняем инструкцию вида MOV ???, - (R3), а
чтобы вытолкнуть элемент из стека, используем MOV (R3)+, ???.
Возвращаясь к нашему примеру со стопкой тарелок на поверхности при-
лавка, мы вспоминаем, что удаление или добавление тарелок в середине стоп-
146
Нижние адреса 002400 002402 002404 002406 002410 002412 002414 ^-/^Окончательная позиция R3 (после 3-й инструкции MOV) -*— Позиция R3 после 2-й инструкции MOV *-— Позиция R3 после 1-й инструкции MOV *- — Первоначальная позиция R3
с (A0DR}
c(R1)
17
Верхние
адреса
Рис. 8.2.3
Рис. 8.2.2
ки было недопустимо. Однако ничто не мешает нам увидеть, что четвертая
сверху тарелка голубая, что вторая тарелка с отбитым краем и т. п. То есть
можно осуществлять доступ к любому элементу стека при условии, что ни-
каких удалений или добавлений не происходит нигде за исключением верши-
ны стека. Пример прояснит эту идею.
Предположим, что где-то в оперативной памяти мы имеем установленный
стек и R1 используется в качестве указателя стека; предположим также, что
стек содержит элементы, показанные на рис. 8.2.3. Как видно из диаграммы,
стек организован так, что растет вверх — от верхних адресов к нижним. Рас-
смотрим последовательность инструкций
ADD6(R1),R5
ADD (Rl), R5
ADD 2(R1),R5
Эти инструкции прибавляют к текущему содержимому регистра 5 числа
6066, 4022 и 177763 в указанном порядке. Таким образом, был осуществлен
доступ к трем элементам стека, но сам стек как структура, содержащая че-
тыре элемента, не был изменен. Мы даже можем использовать такую ин-
струкцию, как COM 4(R1), которая изменяет элемент 24 на 177753. Опять
на саму структуру воздействие не было оказано, хотя один из ее элементов и
был изменен.
Мы представили два различных (фактически противоположных) способа
организации стека. При первом способе стек растет вниз — от нижних адре-
сов к верхним. При втором - ситуация обратная. Вторая схема кажется не-
сколько более привлекательной, поскольку на картинке создается впечатле-
ние, что стек растет вверх, и указатель стека действительно показывает на
верхний элемент стека. В оставшейся части книги мы обычно будем исполь-
зовать эту вторую схему, и по причинам более серьезным, нежели просто
эстетические. Но важно понимать, что и в первой схеме нет ничего неверного.
Для нее требуется другой тип ведения стека, но пока программист знает, как
147
стек организован, никаких проблем не встречается. Действительно, нет ниче-
го необычного, когда одна и та же программа содержит оба типа стеков.
У читателя может возникнуть законный вопрос о полезности стековой
структуры. Мы описали эту структуру и увидели, что ее реализация в опера-
тивной памяти весьма проста. Она может использоваться как место для хра-
нения элементов, но схема сохранения (всегда на одном конце) на первый
взгляд представляется, конечно, странной. Кажется, что нет причин, почему
бы стек был больше или меньше полезен, чем любой другой блок памяти.
В этом отношении мы просим читателя набраться терпения на несколько раз-
делов. Мы убедим его, что стек является одним из самых полезных приспо-
соблений в инструментарии программиста.
8.3. НЕКОТОРЫЕ ПОТЕНЦИАЛЬНЫЕ ПРОБЛЕМЫ ПРИ ВЕДЕНИИ СТЕКА
Если мы решаем реализовать в программе стековую структуру, то долж-
ны зарезервировать в оперативной памяти некоторую область, в которой
стек будет находиться. Тогда возникает фундаментальный вопрос о том, как
много памяти должно быть зарезервировано. Чтобы увидеть, как возникают
проблемы ведения стека в результате неправильного ответа на этот вопрос,
рассмотрим программу, в которой блок из восьми слов резервируется не
только под стек, но также и под временную память нестековой природы.
001000 BLOCK: .BLKW 10
001002
001004
001006
001010
001012
001014
001016
001020 START: MOV # START, R4
001022
001024
Инструкция в ячейке START —MOV # START, R4 — инициализирует регистр
R4, присваивая ему значение 001020, так что он может быть использован в
качестве указателя стека пользователя. Заметим, что, хотя c(R4) = 001020,
база стека будет находиться в 001016, поскольку первое проталкивание в
стек, MOV ???, — (R4), перед пересылкой автоматически уменьшит c(R4)
до 001016. Предположим теперь, что программисту понадобилось временно
сохранить содержимое регистра R0, очевидно, какие-то нужные для програм-
мы данные, но в настоящий момент регистр необходим для других целей. Ве-
роятным местом сохранения содержимого этого регистра является пользова-
тельский стек, поэтому программа выполняет инструкцию MOV R0, — (R4).
Содержимое R0 будет переслано в 001016, причем 001016 будет также адре-
сом в указателе стека R4. Теперь предположим, что, вероятно, с помощью
$ IN. OCT #BLOCK, #6 нужно выполнить чтение шести чисел. Эти числа будут
размещены в ячейках от BLOCK до BLOCK + 12 (от 001000 до 001012) вклю-
чительно. Пока все идет хорошо, но предположим теперь, что производятся
два дополнительных проталкивания в стек. Первое проталкивание перешлет
148
число в ячейку с адресом 001014, но второе проталкивание —в ячейку 001012,
тем самым перекрывая шестое число, считанное до этого.
Проблема очевидна. Когда был инициализирован указатель стека, стек
имел потенциальную возможность расширения до восьми элементов, но чте-
ние шести фрагментов данных в массиве BLOCK немедленно уменьшило сте-
ковое пространство до двух элементов. После этого третье проталкивание в
стек привело к переполнению стека (конечно, если бы к моменту третьего
проталкивания ранее прочитанные данные потеряли свое значение, то никако-
го повреждения не произошло бы). В хорошо написанных программах тако-
го двойного использования одного блока памяти, делающего его динамичес-
кой областью памяти, тщательно избегают. Намного лучше выделять память,
которая будет использоваться исключительно под стек, а для других нужд
резервировать другие блоки. Но даже и при таких предосторожностях мы
все-таки можем недооценить необходимый размер стека и возникнет его пе-
реполнение.
Возможна также противоположная проблема. Предположим, например,
что дан первоначально пустой стек и мы проталкиваем в него три элемента.
Какое-то время спустя мы выталкиваем эти три элемента из стека. Но теперь,
в результате плохого ведения стека, мы пытаемся выполнить дополнитель-
ное выталкивание из стека. Конечно, что-нибудь из ’’стека” будет вытолкну-
то, но поскольку программа ничего туда не проталкивала, без сомнения эти
данные окажутся для нее бессмысленными. Здесь мы столкнулись с исчерпа-
нием стека. Переполнение и исчерпание стека происходят в результате плохо-
го его ведения, и программист должен предусматривать для обработки таких
ситуаций соответствующие процедуры. Если стек используется часто и интен-
сивно, то единственно, что поможет избежать таких проблем, — это повышен-
ное внимание.
Хотя с описанными выше ситуациями мы справляемся с помощью внима-
тельного и расчетливого подхода, еще одну проблему решить не-так легко.
Предположим опять, что у нас есть стек с указателем стека R4, содержащим ад-
рес 001020, и мы полагаем, что первоначально стек пустой. Предположим так-
же, что в стек проталкивается содержимое регистра R0, т. е. выполняется ин-
струкция MOV R0, -(R4), так что c(R4) предварительно автоматически
уменьшается до 001016. Предположим, что нам необходимо сохранить байт
с символьным адресом ADDR + 1, и мы решаем протолкнуть его в стек. Мы
можем это сделать с помощью инструкции MOVB ADDR + 1, — (R4). Тогда
c(R4) предварительно автоматически уменьшается на единицу — до 001015,
и байт пересылается в зту ячейку (вспоминаем, что число,на которое содер-
жимое регистра автоматически увеличивается или уменьшается, зависит от
того, является ли инструкция пословной или байтовой). Если в дальнейшем
работа со стеком проходит в пословной форме, например при выполнении
MOV (R5), — (R4), то возникают затруднения, поскольку указатель стека R4
в настоящее время содержит нечетный адрес (001015), а процессор не может
переслать слово, если адрес приемника нечетный.
По существу, имеются три способа справиться с этой трудностью. Первый
включает в себя ведение двух стеков: одного для проталкивания и выталки-
вания байтов, другого — для проталкивания и выталкивания слов. Поэтому
всякий раз, когда в стек нужно поместить байт, он проталкивается в байто-
149
вый стек, слова же следуют в пословный стек. Как обычно, программист
должен быть внимательным при ведении двух стеков, но сама проблема бай-
тов/слов полностью исчезает, и такое решение ни в коей мере не является не-
обоснованным или необычным.
Второй способ решения этой проблемы представляется как раз необосно-
ванным и необычным, но его имеет смысл обсудить здесь, так как он есте-
ственно ведет к третьему решению. Поскольку нам известно, что трудность
заключается в том, что указатель стека может содержать нечетный адрес, нам
необходима какая-то схема, гарантирующая, что указатель стека всегда со-
держит четный адрес. Мы можем достичь этого, если всякий раз, когда в стек
проталкивается байт, сразу же после этого проталкивания будем выполнять
инструкцию, подстраивающую указатель стека дополнительно на 1, так что
он опять будет содержать четный адрес. Например, инструкции
MOVB ADDR+1,-(R4)
DEC R4
будут автоматически уменьшать c(R4) на 1, перемещать байт с адресом
ADDR + 1 в ячейку, адрес которой находится в R4, и затем подстраивать с(R4)
до четного числа с помощьюинструкции DEC R4. Конечно, всякий раз перед
выталкиванием байта из стека необходимо делать аналогичную подстройку с
помощью инструкции INC R4.
Обратите внимание, что одним из последствий применения этой схемы яв-
ляется то, что каждый раз, когда байт проталкивается в стек, последующая
подстройка указателя стека оставляет в стеке неиспользованный (потерян-
ный) байт. Если число байтов, проталкиваемых в стек за один раз этим спо-
собом, невелико, то реальных возражений не возникает, поскольку количе-
ство потерянной памяти будет незначительно по отношению к объему доступ-
ной оперативной памяти. Более серьезное последствие состоит в том, что те-
перь проталкивание и выталкивание элементов представляют собой неедино-
образный процесс - байтовые операции требуют упомянутых выше подстро-
ек, тогда как пословные операции явным образом требуют, чтобы таких под-
строек не делалось. Очевидно, что этот способ приводит к дополнительной
нагрузке на программиста и соответственно увеличивает вероятность ошибок.
Несмотря на эти сложности, очерченное выше решение проблемы байтов/
слов, при котором гарантируется, что указатель стека подстраивается на 2,
представляет интерес, и имеет смысл его придерживаться. Напоминаем чита-
телю, что в предыдущей главе мы утверждали, что один из регистров - R6 —
всегда подстраивался на 2 при использовании в режимах автоувеличения и
автоуменьшения (режимах 2 и 4) и никогда на 1, независимо от того, была
ли инструкции пословной или байтовой1. Таким образом, R6 представляется
естественным регистром для использования в качестве указателя стека; про-
талкивание или выталкивание слова из стека, на которое указывает R6, при-
1 В сноске на с. 112 мы указывали, что процессоры некоторых моделей ЭВМ PDP-11
выполняют автоувеличейне и автоуменымение R6 несколько иначе, чем здесь описано.
Однако даже на таких машинах стек ведется правильно. Например, при смешанном про-
талкивании байтов и слов слова будут правильно выстраиваться по четным (послов-
ным) адресам. Следовательно, можно считать, что любой процессор ведет себя так, как
отмечено выше.
150
водит к подстройке c(R6) на 2, а проталкивание и выталкивание байта так-
же осуществляют подстройку с (R6) на 2 (с соответствующей потерей одного
байта). Таким образом, когда R6 используется в качестве указателя стека,
проблема байтов/слов просто исчезает. Теперь должно быть ясно, что реак-
ция R6 на режимы адресации 2 и 4, которая до сих пор могла казаться читате-
лю весьма своеобразной, проистекает именно из того факта, что R6 предназна-
чается для использования в качестве указателя стека. И поскольку роль R6
в этом отношении настолько велика, этот регистр обычно обозначают как SP
(Stack Pointer — указатель стека) по аналогии с тем, как обозначение PC ис-
пользуется для R7. Присваивая R6 такое специальное название (SP), мы ни в
коей мере не хотим создать у читателя впечатление, что R6 является един-
ственным регистром, который может использоваться в качестве указателя
стека. Все, что мы говорили до сих пор, соответствует истине; любой регистр,
отличный от R7, можно использовать как указатель стека. Просто регистр 6
обладает определенным свойством по отношению к некоторым режимам ад-
ресации, которое делает его особенно полезным в этом смысле.
8.4. СПЕЦИАЛЬНЫЙ УКАЗАТЕЛЬ СТЕКА SP (R6) И АППАРАТНЫЙ СТЕК
При обсуждении программ и программных сегментов в оставшейся части
книги мы будем полагать, что всякий раз, когда это необходимо, в оператив-
ной памяти устанавливается стек, на который указывает SP. Поэтому, Когда
мы ссылаемся на указатель стека, мы всегда имеем в виду SP (R6), а когда
ссылаемся на стек, то имеем в виду тот стек, на который указывает SP. Ес^и
в программе устанавливаются также и другие стеки, мы будем ссылаться на
них более явным образом, например, как ’’стек, на который указывает R3”,
или, например, ’’байтовый стек”.
Обычно мы не будем явным образом интересоваться, где размещается
стек и как инициализируется SP, а будем полагать, что этому уже уделено со-
ответствующее внимание. Опасения читателя, которому нелегко справиться
с отсутствием определенности, будут рассеяны в следующей главе. Стек мо-
жет быть установлен целым рядом способов. Например, мы можем согла-
ситься, что программа загружается, начиная с ячейки 001000 и что указатель
стека инициализируется этим же самым значением. Таким образом, ячейки
323333
циалыюй длине стека 256ю слов, чего при обычных обстоятельствах более
чем достаточно (рис. 8.4.1,а). В качестве альтернативного способа можно
инициализировать указатель стека так, чтобы он указывал на адрес, располо-
женный на несколько сотен слов дальше последнего адреса нашей програм-
Стек
Программа
Стек
Программа
Рис. 8.4.1
б)
151
1 .TITLE MAXIMUM
2 5
3 000000 012700 START: MOV S4»R0 СРАВНИВАТЬ 4 РАЗА
000004
4 000004 012702 MOV #DATA»R2 - ;АДРЕС 1-Г0 ЧИСЛА В R2
000072’
5 000010 012246 MOV <R2) + >-<SP) Я-Е ЧИСЛО = ВРЕМ. МАКСИМУМ
6 000012 021622 LOOP: CMP <SP)f<R2)+ СРАВНИТЬ ВРЕМ. МАКСИМУМ С ЧИСЛОМ
7 000014 002002 BGE NEXT JBPEM. МАКСИМУМ ВОЛЬВЕ — ПРОПУСТИТЬ
8 000016 016216 MOV -2<R2)»<SP) .ЗАМЕНИТЬ ВРЕМ. МАКСИМУМ
177776
9 000022 005300 NEXT: DEC R0 ;УМЕНЬВИТЬ СЧЕТЧИК НА 1
10 000024 001372 BNE LOOP СЛЕДУВШЕЕ ЧИСЛО» ЕСЛИ НЕ НОЛЬ
11 000026 012667 MOV <SP)+>ANSWER :ПОЛУЧИТЬ МАКСИМУМ
000036
12 000032 SOUT.OCT 4ANSWER»tl ; И РАСПЕЧАТАТЬ ЕГО
13 000066 «EXIT ;ВЕРНУТЬСЯ К ОПЕРАЦИОННОЙ СИСТЕМЕ
14
15 000070 ANSWER: . BLKW 1 ;ДЛЯ РАСПЕЧАТКИ МАКСИМУМА
16
17 ?ДОЛЖЕН БЫТЬ НАЙДЕН МАКСИМУМ СРЕДИ СЛЕДУВШИХ 5 ЧИСЕЛ
18
19 000072 000403 DATA: .WORD 403
20 000074 102304 .WORD 102304
21 000076 023704 .WORD 23704
22 000100 007702 .WORD 7702
23 000102 176337 .WORD 176337
24 f
25 000000’ .END START
Рис. 8.4.2
мы (рис. 8.4.1, б). В любом случае мы устанавливаем стековое пространство,
достаточное для большинства потребностей. Обратите внимание, что такой
щедрый размер стека почти совсем устраняет опасность его переполнения, за
исключением необычных случаев, но не снимает потенциальную возможность
исчерпания стека. То, как стек создается, нас здесь не интересует; будем про-
сто полагать, что он существует, имеет адекватный размер и мы свободны ис-
пользовать его всякий раз, когда он нам покажется полезным.
Стек, указателем которого является R6, называется аппаратным стеком,
и этот термин требует некоторых пояснений. В действительности каждый
стек является аппаратным в том смысле, что он располагается в оперативной
памяти и, таким образом, конечно, аппаратно-зависим. Но стек, на который
указывает SP, единственный в своем роде из-за того, что существуют аппарат-
ные инструкции, которые его используют. То есть в процессе выполнения не-
которых инструкций сам ЦП использует этот стек для проталкивания и вы-
талкивания элементов. Ни с одной из таких инструкций мы еще не встреча-
лись, поскольку MOV, INC, СМР и т. п. стека не используют, но такие ин-
струкции вскоре появятся. Пока же мы утверждаем, что наличие или отсут-
ствие такого стека не есть вопрос произвольного суждения — такой стек дол-
жен существовать для правильного выполнения некоторых аппаратных ин-
струкций.
Прежде чем закончить этот раздел, предлагаем пример, показывающий,
как стек может использоваться для улучшения программирования. На рис.
8.4.2 показана пересмотренная версия программы нахождения максимально-
го числа (см. рис. 7.5.3), в которой стек используется для удержания вре-
менного максимума; тем самым исключается необходимость в дополнитель-
ном регистре (R5). Читателю рекомендуется обратить особое внимание на
строку 5, в которой выполняется проталкивание-, на строку 8, в которой вы-
полняется не проталкивание, а замена верхнего элемента стека; и на строку
11, в которой выполняется выталкивание, что возвращает стек к первона-
чальному состоянию, т. е. мы ’’вычищаем” стек перед тем, как выйти из про-
граммы (выполнить EXIT).
8.5. ПОДПРОГРАММЫ
В процессе программирования мы нередко обнаруживаем, что в одной и
той же программе приходится неоднократно повторять одну и ту же группу
инструкций. В качестве примера рассмотрим программу, манипулирующую
матрицей, в которой блок из 25ю слов, первое из которых имеет метку
MATRIX, интерпретируется нами как матрица (или массив) размером 5x5.
Первая строка матрицы состоит из слов в ячейках MATRIX, MATRIX + 2,
MATRIX + 4, MATRIX + 6 и MATRIX + 10, вторая строка - из слов от MAT-
RIX + 12 до MATRIX + 22 и т. д. Манипулируя этой матрицей, мы, вероятно,
захотим посмотреть, какие значения она будет содержать после каждого су-
щественного изменения ее элементов. Поэтому нам потребуется распечатать
содержимое этого блока памяти (скажем, в десятичном виде), но простое
использование команды 8OUT.DEC #MATRIX, #31 даст в результате две
строки по девять чисел в каждой, за которыми будет следовать одна строка
из семи чисел (см. приложение Б, где описан вывод команды $OUT.DEC).
Очевидно, что для структуры, которую мы наложили на этот блок чисел, та-
кая форма вывода не особенно удобна. Поэтому выполним некоторое фор-
матирование, используя пять команд
$OUT.DEC #MATRIX, #5
SOUT.DEC #MATRIX + 12, #5
$OUT.DEC #MATRIX + 24, #5
SOUT.DEC #MATRIX + 36, #5
$OUT.DEC #MATRIX+ 50,#5
Эти команды отлично выполнят необходимую работу, и мы увидим пер-
вые пять чисел блока (которые мы интерпретируем как первую строку мат-
рицы), за которыми последуют следующие пять чисел и т. д. И хотя такой
формат нас может устраивать, включение этих пяти строк исходного кода
каждый раз, когда нужно распечатать матрицу, по двум причинам встречает
возражения. Во-первых, надоедает писать эти пять строк каждый раз, когда
мы хотим увидеть значения матрицы. Во-вторых, при ассемблировании этих
пяти строк исходного кода занимается изрядный объем оперативной памяти.
(Мы уже видели, что обращение к одной из команд ввода-вывода составляет-
ся из нескольких инструкций ЭВМ PDP-11. Команда SOUT.DEC потребляет
168 слов памяти, так что для распечатки пяти строк матрицы нужно 5x16 =
= 1068 = 70ю слов.)
Очевидно, что нам необходим какой-то метод, чтобы мы могли записать
эти пять команд вывода только один раз в виде отдельной стандартной про-
граммы, называемой подпрограммой, которая должна помещаться где-то
за пределами основного потока выполнения программы. Когда же нам потре-
буется распечатать матрицу, мы просто передали бы управление этой под-
программе, например с помощью инструкции JMP или BR. Этот метод решает
проблему нескольких копий исходного кода, но вводит другую, более серьез-
152
153
JMP PRINT
(Следующая инструкция)
JMP PRINT
(Следующая инструкция)
PRINT;
U
(Возврат)
Рис. 8.5.1
--1
ную трудность. Когда нам необходимо рас-
печатать матрицу, мы передаем управление
этой подпрограмме, которая выполняется
и распечатывает, как и требуется, пять строк
матрицы. Но что должно произойти вслед за
этим? Очевидно, необходимо вернуть управ-
ление в основную часть программы, но куда?
Ясно, что мы не можем просто выполнить
переход (JMP) в некоторую фиксированную
ячейку, поскольку нашим намерением при
написании этих пяти строк в виде подпро-
граммы было дать возможность переходить
к ней из различных мест основной программы,
и вероятнее всего, мы захотим вернуть управ-
ление в то место основной программы, отку-
да был выполнен переход к подпрограмме.
Поэтому подпрограмма должна содержать
также какие-то инструкции для выполнения
правильного возврата к основной программе.
Суммируя все это, можно сказать: для того чтобы подпрограмма была полез-
ной, мы каким-то образом должны выполнить следующее.
1. Передать управление подпрограмме из основной части программы (с по-
мощью инструкций JMP или BR).
2. Выполнить подпрограмму.
3. Вернуть управление в основную часть программы к ячейке, которая сле-
дует за инструкцией, передавшей управление подпрограмме.
Эта ситуация в виде диаграммы показана на рис. 85.1, где первое слово под-
программы помечено меткой PRINT, а сплошные и штриховые линии обозна-
чают передачу управления этой подпрограмме из двух разных мест основной
программы и затем возврат к основной программе в соответствующее место.
Должно быть понятно, что какие бы трудности ни возникали, они происте-
кают из концепции ’’возврат из подпрограммы”, поэтому мы сосредоточим
свои усилия именно на этом. Когда мы передаем управление подпрограмме
PRINT, происходит то же, что и при любом типе инструкций перехода или вет-
вления — PC получает значение PRINT. Поскольку при этом то значение, ко-
торое PC имел в основной части программы, теряется, после завершения вы-
полнения подпрограммы не остается никаких надежд вернуться назад в ос-
новную программу, если только перед переходом к подпрограмме мы не от-
метили как-то, каким было значение PC к моменту передачу управления к
PRINT. Мы в состоянии сделать это с помощью тех инструкций, которые в
настоящее время имеются в нашем распоряжении.
Поскольку перед передачей управления подпрограмме нам необходимо
сохранить текущее значение PC, мы сохраним его в регистре, например в
R5, с помощью инструкции MOV PC, R5. (Читатель может посчитать,что стек
в равной степени является подходящим местом для сохранения значения
PC - с помощью MOV PC, -(SP) - и это совершенно правильно. Однако по-
ка мы предпочитаем использовать для этой цели регистр общего назначения.)
154
Ниже Представлена схема перехода к подпрограмме и возврата из нее, кото-
рая почти работоспособна (показанные адреса выбраны произвольно):
024612
024614 010705 MOV PC, R5
024616 000167 JMP PRINT
024620 024622 024624 021622 (следующая инструкция)
046444 PRINT: (первая инструкция под- программы)
046552 010507 MOV R5,PC
Когда выполняется инструкция MOV PC, R5, число (адрес) 024616 помещает-
ся в R5, поскольку с (PC) уже было увеличено на 2 при извлечении инструк-
ции. Затем управление передается подпрограмме с помощью инструкции
JMP PRINT. Инструкция в ячейке 046552 — MOV R5, PC — помещает число '
024616 в PC; таким образом, после возврата из подпрограммы выполнение
программы возобновится с ячейки 024616. К несчастью, это не совсем то ме-
сто, к которому мы хотели бы вернуться, ведь нам нужно 024622. Но эта
ситуация легко может быть исправлена двумя различными способами.
Когда мы готовы вернуться из подпрограммы, R5 содержит не адрес воз-
врата, а адрес инструкции перехода. Поскольку нам надо перешагнуть через
два слова (четыре байта), составляющих инструкцию JMP, все, что необходи-
мо сделать, это подстроить R5 перед пересылкой его содержимого назад в
программный счетчик. Таким образом, если бы мы изменили инструкцию
MOV R5, PC на
046552 062705 ADD #4, R5
046554 000004
046556 010507 MOVR5,PC
го перед тем, как содержимое R5 было бы переслано в PC, R5 содержал бы
значение 024622 — правильный адрес возврата.
Второй способ исправления почти такой же, как й первый, за исключени-
ем того, что в этом случае соответствующая подстройка содержимого R5 вы-
полняется перед передачей управления подпрограмме. Обратите внимание,
что в этом случае мы не можем, как прежде, просто прибавить 4 к c(R5).
Вместо этого, к c(R5) мы должны прибавить 10, чтобы учесть четыре байта,
занимаемые самой инструкцией ADD.
024612
024614 010705 MOV PC, R5
024616 062705 ADD # 10, R5
024620 000010
024622 000167 JMP PRINT
024624 021616
155
024626
024630
046444
(следующая инструкция)
PRINT: (первая инструкция под-
программы)
046552 010507 MOV R5,PC
Хотя этот второй подход к формированию правильного адреса возврата
представляется несколько более сложным (с точки зрения программиста),
мы предпочитаем его по причинам, которые вскоре станут ясными. Прежде
всего мы привлекаем внимание к тому факту, что регистр R5, использован-
ный для сохранения адреса возврата, в самой программе может использовать-
ся для других целей. Поэтому, чтобы избежать неприятностей, которые мо-
гут произойти при сохранении в нем с(РС), хорошая практика заключалась
бы в сохранении c(R5) до его использования и восстановлении его первона-
чального значения после возврата из подпрограммы. Поскольку наиболее
вероятным местом для временного хранения c(R5) представляется стек, мы
модифицируем последовательность перехода к подпрограмме следующим
образом:
MOV R5, — (SP)
MOV PC, R5
ADD #10, R5
JMP PRINT
MOV (SP)+,R5
(следующая инструкция)
Теперь мы располагаем методом передачи управления подпрограмме, а
также правильного возврата к основной части программы. Метод представля-
ется работоспособным при любых обстоятельствах, но в действительности
все-таки еще есть некоторые потенциальные проблемы. Предположим, напри-
мер, что в ходе выполнения основной части программы нам потребовалось
распечатать матрицу, т. е. передать управление подпрограмме PRINT; но мы
обнаруживаем, что находимся настолько физически близко к подпрограмме,
что можем достигнуть ее с помощью инструкции BR, а не JMP. Если мы вста-
вим такой код перехода к подпрограмме:
MOVR5, — (SP)
MOV PC, R5
ADD #10, R5
BR PRINT
MOV(SP)+,R5
(следующая инструкция)
то обнаружим, что неверно подстроили c(R5) с помощью инструкции ADD
#10, R5, так как для BR PRINT требуется не два слова, как для JMP PRINT,
а одно, и, следовательно, подстройка должна выполняться с помощью ADD
#6, R5. Пока разум нам не отказывает, при написании инструкций передачи
156
управления подпрограмме все будет хорошо, однако процедура начинает ста-
новиться несколько обременительной. Но обратите внимание, что если мы
решаем сделать подстройку R5 в самой подпрограмме, как предложено на
с. 155, то остается мало надежд на правильный возврат. Если, как показано,
мы подстраиваем c(R5) на 4, то инструкция JMP PRINT осуществит возврат
правильно, но BR PRINT — нет. В то же время, если c(R5) подстраивается на
2, то BR PRINT будет осуществлять возврат правильно,тогда как JMPPRINT—
нет. По этой причине мы предпочитаем подстраивать R5 в основной части про-
граммы, где мы можем определить, как передается управление подпрограм-
ме, и, таким образом, правильно подстроить адрес возврата.
И последнее, R5 не играет здесь никакой специальной роли. Значение PC
для возврата может быть сохранено в любом регистре общего назначения, и,
как упоминалось раньше, даже стек может оказаться подходящим для этого.
Действительно, пока адрес возврата (возможно, подстроенный) где-то сохра-
няется, так что управление может быть возвращено к основной программе
после выполнения подпрограммы, схема перехода к подпрограмме и возвра-
та из нее не только работоспособна, но и в высшей степени полезна.
8.6. ИНСТРУКЦИИ JSR И RTS
Предыдущий раздел должен убедить читателя в том, что понятие подпро-
граммы весьма полезно для уменьшения объема кода, необходимого в неко-
торых программах, и; следовательно, при этом отпадает необходимость напи-
сания этой части кода программистом. Но инструкции, необходимые для
установки адреса возврата перед переходом к подпрограмме, хотя концепту-
ально и несложные, тем не менее требуют определенного внимания и, как
всегда, это сопровождается повышением вероятности ошибок. Кроме того,
сами инструкции занимают некоторый объем оперативной памяти. К счастью,
аппаратура ЭВМ PDP-11 оказывает большую помощь в этом отношении.
Краткий обзор скемы перехода к подпрограмме и возврата из нее выявля-
ет следующую процедуру.
1. Сохранить содержимое некоторого регистра в стеке.
2. Поместить (правильный) адрес возврата в этот регистр.
3. Передать управление подпрограмме.
4. Установить в PC адрес возврата и восстановить (первоначальное) значе-
ние регистра.
Именно этот эффект дают две аппаратные инструкции: JSR (Jump to Sub-
Routine - перейти к подпрограмме) и RTS (ReTum from Subroutine — вер-
нуться из подпрограммы). Формат инструкции JSR такой:
JSR Кп,цель
В двоичном коде:
0 000 100 rrr ddd ddd
в восьмеричном представлении *
004Я£)£)
Здесь R — это регистр, используемый для сохранения возвратного значе-
ния PC (в примерах предыдущего раздела это был R5), называемый регист-
ром для перехода к подпрограмме, DD, как обычно, представляет приемную
157
часть перехода к подпрограмме, и здесь возможны все режимы адресации.
Инструкция JSR выполняет следующие действия.
1. Вычисляет адрес приемника (подпрограммы).
2. Проталкивает текущее содержимое регистра для перехода в аппаратный
стек.
3. Помещает текущее содержимое PC в регистр для перехода.
4. Помещает адрес приемника (подпрограммы) в PC, т. е. выполняется пе-
реход к подпрограмме.
Детали инструкции JSR могут быть показаны с помощью такой диаграм-
мы:
TEMP адрес приемника (подпрограммы)
Ф (SP) *- с (регистр для перехода)
регистр для перехода <- с (текущее значение PC)
PC с (TEMP)
Здесь символ <- обозначает ’’придается значение . . .”, символ l(SP) — про-
талкивание в стек, a TEMP — это собственный внутренний регистр ЦП.
Важно, чтобы читатель понимал, что все эти желательные действия выпол-
няются в результате одной инструкции JSR. Сохранение текущего содержи-
мого регистра для перехода, сохранение текущего значения PC и переход к
подпрограмме теперь полностью выполняются ЦП, а программисту остается
не. больше, чем написать саму инструкцию JSR. Здесь необходимо сделать
два замечания. Во-первых, эта инструкция действительно использует аппарат-
ный стек и является первой встретившейся нам инструкцией такого рода.
Это также пока что самая сложная инструкция из всех, которые мы рассмат-
ривали, с точки зрения объема действий, выполняемых ЦП. Во-вторых, адрес
возврата всегда правильный, так как независимо от формата инструкции JSR
(она может быть однословной или двухсловной, такой как JSR R5, PRINT,
или JSR R0, @#SUBR, или JSR R0, (R3)) PC всегда содержит адрес следую-
щей инструкции, которая располагается за JSR к моменту, когда .текущее
с (PC) пересылается в регистр для перехода.
Теперь нам нужна парная инструкция для выполнения возврата из под-
программы, и таковой является инструкция RTS. Формат ее такой:
RTS Rn
В двоичном коде:
0 000 000 010 000 ггг
в восьмеричном представлении:
00020/?
где R — регистр для перехода. Действие этой инструкции в виде диаграммы
изображается так:
PC <- с (регистр для перехода)
регистр для перехода (SP) t
где (SP) t обозначает выталкивание из аппаратного стека.
Представим здесь окончательную версию основного примера подпрограм-
мы из предыдущего раздела.
158
024612
024614 004567
024616 021624
024620
JSR R5, PRINT
(следующая инструкция)
046444 PRINT: (первая инструкция под-
программы)
046552 000205 RTS R5
Когда PC содержит число (адрес) 024614, ЦП производит извлечение, увели-
чивая с (PC) до 024616. Поскольку инструкция — индексированная (режим 6
с PC), то ЦП выполняет еще одно извлечение, увеличивая с (PC) до 025620.
Теперь выполняется инструкция JSR. Текущее содержимое R5 помещается
в стек, R5 получает текущее значение с (PC), равное 024620, а управление пе-
редается к PRINT. Когда встречается инструкция RTS R5, ЦП помещает те-
кущее содержимое R5, а именно 024620, в PC и выталкивает из стека содер-
жимое R5. Таким образом, управление возвращается в ячейку 024620, к ин-
струкции, следующей 3aJSR.
Пожалуй, это все, что необходимо было сказать о комбинации JSR/RTS-
Эти инструкции совершенно автоматически выполняют передачу управления
подпрограммам и соответствующие возвраты. И хотя программисту необхо-
димо (как мы увидим в одном из поел еду кшДих разделов) знать, что проис-
ходит, когда выполняются эти аппаратные инструкции, тем не менее процес-
сор и его аппаратура берут на себя существенную часть действий программи-
ста, в особенности по установке адреса возврата.
Наконец, мы полагаем очевидным, что если регистр для перехода модифи-
цируется внутри подпрограммы, к которой он относится, то последствия для
инструкции RTS могут быть драматическими, поскольку этот регистр содер-
жит адрес возврата - жизненно важную ниточку к основной части програм-
мы. В разд. 8 В мы увидим, что модификации регистра для перехода, сделан-
ные в подпрограмме, могут быть желательными и необходимыми, но они
должны выполняться очень внимательно.
8.7. ПОДПРОГРАММЫ, ОБРАЩАЮЩИЕСЯ К ДРУГИМ ПОДПРОГРАММАМ
В предыдущем разделе мы видели, как комбинация инструкций JSR/RTS
(которые делают все, что необходимо для обращения к подпрограмме) ис-
пользует аппаратный стек для сохранения содержимого регистра, отведенно-
го для перехода. И хотя мы понимаем желательность сохранения этого содер-
жимого и можем согласиться, что аппаратный стек является удобным мес-
том для его помещения на время, пока регистр используется для сохранения
возвратного значения PC, та роль, которую играет структура стека, пока еще
не ясна. В разд. 8.2 мы расхваливали стек как в высшей степени полезное при-
способление; теперь настала пора оправдать эту рекламу.
159
Рассмотрим намного более сложную ситуацию с подпрограммами, чем в
предыдущих разделах, а именно такую, где фигурируют три подпрограммы,
которые мы будем называть SUBA, SUBB и SUBC. Полагаем также, что к
ним обращается какая-то основная программа. Для удобства введем некото-
рую терминологию. Основную часть программы будем называть стержневой
ветвью программы, а всякий раз при ссылке на подпрограмму будем гово-
рить, что мы вызываем ее или обращаемся к ней. В настоящем примере мы
всячески стараемся усложнить ситуацию, насколько это возможно. Вместо
того, чтобы по очереди обращаться из стержневой ветви программы к под-
программам SUBA,,SUBB и SUBC, что не отличалось бы от предыдущих при-
меров, мы полагаем, что стержневая ветвь обращается к SUB A, SUB А в про-
цессе своего выполнения вызывает SUBB, которая, в свою очередь, вызыва-
ет SUBC. Читатель может заключить, что такое вложение обращений к под-
программам не приводит к серьезным трудностям при условии, что при каж-
дом обращении используется другой регистр для перехода. Но мы намере-
ваемся использовать один и тот же регистр — R2 — для всех трех обращений
к подпрограммам. Может показаться, что при обращении к SUBB случится
неприятность, поскольку при этом c(R2) будет разрушено, что лишит воз-
можности SUB А вернуться к стержневой ветви программы. Но, как мы уви-
дим, именно стековая структура полностью устраняет эти трудности.
На рис. 8.7.1 адреса опять назначаем произвольно. Прослеживая обраще-
ния к подпрограммам, особое внимание уделим состоянию аппаратного сте-
ка, что является ключом к правильной обработке обращений к подпрограм-
мам и возвратов из них. Мы предполагаем, что первоначально стек пуст, хотя
никаких особых причин для этого нет. В ячейке 002410 стержневой ветви
программы мы придаем R2 значение 12 только лишь для того, чтобы точно
знать c(R2).
Рассмотрим детально действие инструкции JSR R2, SUB А в ячейке 002414.
При извлечении инструкции JSR из ячейки 002414 ЦП увеличивает с (PC) до
002416. Поскольку эта инструкция — индексированная (режим 6), требуется
еще одно извлечение для получения индекса, 004270. При этом с (PC) увели-
чивается до 002420. Теперь вычисляется адрес приемника путем прибавления
индекса к текущему содержимому PC, что дает: 002420 + 004270 = 006710.
Затем текущее содержимое регистра для перехода R2 (000012) проталкива-
ется в стек, текущее содержимое PC (002420) помещается в R2 и PC получа-
ет значение адреса приемника 006710. Состояние стекав этот момент показа-
но на рис. 8.7.2. Выполнение начинается с подпрограммы SUBA и продолжа-
ется до тех пор, пока в ячейке 006744 не встретится инструкция JSR R2, SUBB.
Опять вычисляется адрес приемника, который оказывается равным 010330,
и в процессе этого с (PC) увеличивается до 006750. Затем текущее содержи-
мое регистра для перехода R2 (002420) помещается в стек, R2 получает зна-
чение PC (006750) , и PC получает значение адреса приемника. Текущее состо-
яние стека показано на рис. 8.7.3. Выполнение подпрограммы SUBB продол-
жается до тех пор, по ка ве встретится инструкция JSR R2, SUBC. Вычисляет-
ся адрес приемника (017022) , текущее содержимое R2 (006750) помещается
в стек, R2 получает текущее значение с (PC) (011406), PC получает значение
адреса приемника (рис. 8.7.4). Теперь происходит выполнение подпрограм-
мы SUBC; оно продолжается до тех пор, пока в ячейке 020034 не встретит-
160
002406 (основная программа)
002410 012702 MOV #12, R2
002412 000012
002414 004267 JSR R2.SUBA
002416 004270
002420 (следующая инструкция стержневой ветви)
002422 •
Q06710 SUBA: (1-я инструкция SUBA)
006744 004267 • JSR R2.SUBB
006746 001360
006750 (следующая инструкция SUBA)
007204 000202 RTS R2
010330 SUBB: (1-я инструкция SUBB) • •
011402 004267 JSR R2.SUBC
011404 005414
011406 (следующая инструкция SUBB) •
011722 000202 • RTS R2
017022 SUBC: (1-я инструкция SUBC) •
020034 000202 • RTS R2
Рис. 8.7.3
c(R2) =006750
c(PC) =010330
SP
161
c(R2) =011406 с (PC) =017022 Рис. 8.7.4 c(R2) =006750 SP с (PC) =011406 Рис. 8.7.5 -*—SP
006750
002420 002420
000012 ' 000012
ся инструкция RTS R2. Ее действие состоит в том, чтобы придать PC текущее
значение регистра для перехода R2, а именно 011406, и затем вытолкнуть
верхний элемент стека в этот регистр. Поэтому после выполнения инструк-
ции RTS R2 различные регистры и стек будут иметь значения, показанные на
рис. 8.75. Поскольку с(РС) = 011406, выполнение будет продолжена в под-
программе SUBB с инструкции, которая непосредственно следует за обраще-
нием к SUBC. Когда в ячейке 011722 встретится инструкция RTS R2, процесс
возврата повторяется: PC получает текущее содержимое R2 (006750) , а верх-
ний элемент стека выталкивается в R2 (рис.
8.7.6). Выполнение возобновляется в под-
программе SUBA с инструкции, следую-
щей сразу за обращением из SUBA к SUBB
(006*750), и продолжается до тех пор, по-
ка в ячейке 007204 не встретится RTS R2-
Опять PC получает значение R2 (002420),
верхний элемент стека выталкивается в R2,
возвращая, таким образом, R2 первона-
чальное значение 12. В конце концов вы-
полнение возвращается в стержневую
ветвь программы, где оно было прервано,
а именно, к месту вызова подпрограммы SUBA, а стек опять оказывается
пустым.
Читателю, внимательно проследившему детали этих вложенных обраще-
ний к подпрограммам, должно быть понятно, что причиной правильной обра-
ботки возвратов является то, что при каждом возврате R2 придается послед-
нее значение из тех, которые он имел, — возвраты происходят в обратном по-
рядке по отношению к обращениям. Выполняется все это с помощью сте-
ка как структуры типа ’’последним пришел — первым вышел”; никакой дру-
гой тип структуры не справился бы с этой работой. Действительно, все со-
вершается настолько автоматически, что нам редко приходится интересовать-
ся тем, что же на самом деле происходит при выполнении инструкции JSR и
RTS, поскольку ЦП й стек весьма успешно справляются с этим.
8.8. ПЕРЕДАЧА АРГУМЕНТОВ ПОДПРОГРАММЕ
Единственная конкретная подпрограмма, с которой до сих пор мы имели
дело, а именно, подпрограмма PRINT, отображающая пять строк матрицы
размером 5x5, обладает тем необычным свойством, что всегда выполняет
одну и ту же задачу над одними и теми же ячейками памяти — она всегда рас-
162
печатывает пять чисел, начиная с MATRIX, затем пять чисел, начиная с MAT-
RIX + 12 и т. д. Если же в программе присутствует какая-либо другая матри-
ца, скажем, матрица размером 4x7, размещенная в массиве ARRAY, то, ко-
нечно, длй построчной распечатки ее четырех строк мы не сможем использо-
вать подпрограмму PRINT. Хотя подпрограмма PRINT в определенных ситу-
ациях может быть полезна, она не является типичной подпрограммой, имею-
щей дело с разными параметрами или аргументами — значениями, адресами,
счетчиками и т. п. — при каждом новом обращении к ней.
В качестве примера подпрограммы, оперирующей различными данными,
продолжим наш пример с матрицей и рассмотрим задачу нахождения наи-
большего из некоторого набора чисел (мы уже программировали эту работу
целым рядом способов). Например, может оказаться желательным узнать на-
ибольшее число в матрице или наибольшее число в четвертой строке матрицы
(такого рода задачи часто возникают в разделе математики, называемом ли-
нейным программированием'). Чтобы выполнить эту задачу, подпрограмма
должна знать, с каким числом чисел ей придется иметь дело, а также ей дол-
жен быть известен адрес первого из этих чисел. Поэтому, чтобы подпрограм-
ма вычислила наибольшее число в матрице, необходимо передать ей адрес
первого из этих чисел, а именно, MATRIX вместе с числом чисел 25 ю- Анало-
гично, для нахождения наибольшего числа в четвертой строке подпрограмме
необходимы адрес MATRIX + 36 и счетчик 5. В данном разделе будут описа-
ны некоторые методы, которые могут использоваться для передачи этих ар-
гументов подпрограмме. Разнообразие подобных методов ограничено только
воображением программиста, поэтому едва ли мы смогли бы осветить их все.
Вместо этого представим здесь ряд стандартных приемов; некоторые другие
будут даны в упражнениях. Накопив определенный опыт в этих вопросах, чи-
татель сам сможет придумать полезные схемы.
Чтобы написать подпрограмму максимизации, установим некоторые со-
глашения. Положим, что R1 будет использоваться в качестве регистра для пе-
рехода к подпрограмме, что R0 будет содержать счетчик чисел, подлежащих
максимизации, a R2 — адрес первого из этих чисел. Поскольку задачей под-
программы является вычисление наибольшего из чисел, она должна иметь
возможность каким-то способом сообщить максимальное число стержневой
ветви программы, которая обратилась к подпрограмме. Для этой цели мы
используем R5 — к моменту возврата управления в стержневую программу
из подпрограммы наибольшее искомое число будет находиться в R5. Теперь
мы можем написать ’’скелетный” модуль для выполнения этой работы. Сег-
мент программы, приведенный на рис. 8.8.1, будет выполняться правильно
при условии, что R2 содержит адрес первого числа и R0 содержит число чисел.
Как мы увидим, в зависимости от способа передачи аргументов подпрограм-
ме может потребоваться предварить этот модуль несколькими дополнитель-
ными инструкциями, гарантирующими, что R2 и R0 будут иметь правильные
значения. Назначение первой инструкции подпрограммы, DEC R0, состоит в
том, чтобы R0 содержал число необходимых сравнений, которое всегда на
единицу меньше, чем число чисел.
Легче всего передать необходимую информацию подпрограмме из стерж-
невой ветви, когда сама стержневая программа устанавливает регистры R0
и R2 перед переходом к подпрограмме. Поэтому, если мы просто пометим
6*
163
г
I'СКЕЛЕТНАЯ* ПОДПРОГРАММА МАКСИМИЗАЦИИ
;C<R2> = АДРЕС 1-Г0 ЧИСЛА
002440 002462 002464 002466 002470 002474 002476 005300 012205 020522 002002 016205 177776 077005 000201 LOOP: NEXT: ;C<RO) = СЧЕТЧИК ЧИСЕЛ» » !МАКСИМУМ ВОЗВРАЩАЕТСЯ 1 DEC R0 MOV (R2)+»R5 CMP R5»(R2)+ BOE NEXT NOV -2<R2)»R5 SOB R0*LOOP RTS R1 СРЕДИ КОТОРЫХ ИЖЕТСЯ МАКСИМАЛЬНОЕ В R5 ?УМЕНЬШИТЬ НА 1 ЧИСЛО ; СРАВНЕНИЙ Я-Е ЧИСЛО = ВРЕМ. МАКС. :СРАВНИТЬ ВРЕМ. МАКС. С ЧИСЛОМ 1ВРЕМ. МАКС. Б0ЛМЕ — ПРОПУСТИТЬ :ЗАМЕНИТЬ ВРЕМ. МАКС. :ОБРАБОТАТЬ СЛЕДУЮЩЕЕ ЧИСЛО .’СДЕЛАНО — ВЕРНУТЬСЯ
Рис. 8.8.1
первую инструкцию ’’скелетного” модуля меткой МАХ: и выполним ин-
струкции
MOV #5,R0
MOV #MATRIX + 12, R2
JSR R1,MAX
то подпрограмма вернет в R5 наибольшее число во второй строке матрицы,
и нам не потребуется вносить какие-то дополнения или изменения в ’’скелет-
ную” подпрограмму.
Хотя эта схема, очевидно, представляется самым прямым способом пере-
дачи необходимых аргументов подпрограмме, он может оказаться непрак-
тичным, если для подпрограммы требуется большое количество информа-
ции, поскольку просто не хватит регистров для всех необходимых аргумен-
тов. Поэтому мы предлагаем другую схему, которая хотя и несколько слож-
нее в смысле обработки аргументов, но не налагает никаких ограничений на
число аргументов, передаваемых подпрограмме. Идея состоит в том, чтобы
подлежащие передаче аргументы перечислить в директивах .WORD сразу же
вслед за обращением к подпрограмме. Тогда наш последний пример мог бы
выглядеть так:
JSR R1,MAX
.WORD 5
.WORD MATRIX +12
Сегмент программы для реализации такого обращения к подпрограмме мы
представили на рис. 8.8.2, и, поскольку он содержит целый ряд новых особен-
ностей, разберем их в деталях. Обратите внимание на использование кон-
струкции ”25.” в директиве .BLKW в ячейке 001000. Числа, сопровождаемые
десятичной точкой, трактуются ассемблером как десятичные (по основанию
10), а не как восьмеричные. В остальной части текста мы будем пользовать-
ся преимуществом этой особенности (введенной исключительно для удоб-
ства программиста) всякий раз, когда это окажется полезным.
За обращением к подпрограмме МАХ следуют два слова, содержащие
счетчик и адрес. Когда встречается инструкция JSR-R1, MAX,PC содержит
значение 001724. Вычисляется адрес приемника, c(Rl) помещается в стек,
после чего R1 получает текущее значение PC, которое теперь равно 001730.
Затем PC получает значение адреса приемника и, таким образом, управление
164
001000 MATRIX: "bLKW 25. ;НАТРИНА 5 НА 5
001724 004167 JSR 000520 R1,MAX (НАЙТИ МАКСИМУМ
001730 000005 .HORD •5 ; 5 ЧИСЕЛ,
001732 001734 001012 .WORD MATRIX+12 (СЛЕДУЮЩАЯ ИНСТРУКЦИЯ • ; НАЧИНАЯ С MATRIX+12 ОСНОВНОЙ ПРОГРАММЫ)
(ПОДПРОГРАММА ’НАХ’
002450 010046 МАХ: MOV R0,-ISP) (СОХРАНИТЬ РЕГИСТРЫ
002452 010246 MOV R2,-(SP> { RO И R2 В СТЕКЕ
002454 012100 MOV (R1)+,RO :ПОЛУЧИТЬ СЧЕТЧИК
002456 012102 MOV <R1)+,R2 ( И АДРЕС 1-Г0 ЧИСЛА ;’СКЕЛЕТНАЯ’ ПОДПРОГРАММА МАКСИМИЗАЦИИ (CIR2) = АДРЕС 1-Г0 ЧИСЛА (CIRO) = СЧЕТЧИК ЧИСЕЛ, СРЕДИ КОТОРЫХ ИЩЕТСЯ МАКСИМАЛЬНОЕ ;МАКСИМУМ ВОЗВРАЩАЕТСЯ В R5 9
002460 005300 DEC R0 (УМЕНЬВИТЬ НА 1 ЧИСЛО ; СРАВНЕНИЯ
002462 012205 MOV <R2)+,R5 {1-Е ЧИСЛО ч ВРЕМ. МАКС.
002464 020522 LOOP: CMP R5,(R2) + (СРАВНИТЬ ВРЕМ. МАКС. С ЧИСЛОМ
002466 002002 BGE NEXT {ВРЕМ. МАКС. БОЛЬШЕ — ПРОПУСТИТЬ
002470 016205 177776 MOV -2<R2),R5 {ЗАМЕНИТЬ ВРЕМ. МАКС.
002474 077005 NEXT: SOB R0,LOOP (ОБРАБОТАТЬ СЛЕДУВЩЕЕ ЧИСЛО
002476 012602 MOV (SP)+»R2 .ВОССТАНОВИТЬ РЕГИСТРЫ
002500 012600 MOV ; <SP)+,RO { R0 И R2 ИЗ СТЕКА
002502 000201 Рис. 8.8.2 RTS Rl (СДЕЛАНО — ВЕРНУТЬСЯ
передается подпрограмме МАХ. Самые первые инструкции, выполняемые в
подпрограмме МАХ, помещают в стек содержимое регистров R0 и R2, при-
чем делается это по весьма важной причине. Как мы знаем, в подпрограмме
нужно использовать эти регистры соответственно для счетчика и адреса. Но
ведь нет никакой гарантии, что содержимое этих регистров не имеет значения
для стержневой ветви программы. В предыдущем примере стержневая ветвь
явным образом использовала эти регистры для передачи значений подпро-
грамме. Однако в данном случае эти регистры не были использованы для
этой цели, и поэтому нет причин полагать, что стержневая ветвь не использу-
ет их для каких-то других целей. Таким образом, для безопасности подпро-
грамма сохраняет содержимое этих .регистров в стеке перед тем, как исполь-
зовать их для своих собственных внутренних дел. Мы увидим, что перед воз-
вратом к вызвавшей части программы их значения из стержневой ветви бу-
дут восстановлены.
Следующей (в ячейке 002454) будет выполняться инструкция. MOV
(Rl)+, R0. Какое число будет переслано в R0 и какова здесь цель использо-
вания режима автоувеличения? Напоминаем, что к моменту передачи управ-
ления подпрограмме МАХ содержимое регистра R1 равно 001730. Таким об-
разом, в R0 пересылается содержимое ячейки памяти, адрес которой нахо-
дится в R1, т. е. содержимое ячейки памяти 001730. Следовательно,в R0 по-
падает число 000005, счетчик чисел. Итак, как и требуется, мы разместили
счетчик чисел, подлежащих максимизации, в регистре R0. В то же самое вре-
1.65
мя содержимое Rl автоматически увеличилось до 001732. Следующая ин-
струкция — MOV (Rl)+, R2 — перешлет содержимое ячейки, адрес которой
находится в R1, в R2. Поскольку c(Rl) =001732,в R2пересылается с(001732),
а именно, адрес MATRIX + 12 (001012). Таким образом, опять R2 правильно
установлен на адрес первого числа из подлежащих максимизации, a c(Rl) ав-
томатически увеличено до 001734.
Теперь, когда соответственно в R0 и в R2 размещены правильные счетчик
чисел и адрес, подпрограмма может продолжать работу, как и прежде. Посте
завершения работы, когда все чиста будут сравнены, мы готовы к возврату.
Но прежде чем сделать это, необходимо восстановить из стека значения R0 и
R2, которые они имели в стержневой ветви программы (обратите внимание,
что выталкивание из стека происходит в обратном порядке). Теперь встреча-
ется инструкция RTS R1. Ее действие состоит в пересылке текущего содер-
жимого R1 в PC, а также в выталкивании верхнего элемента стека в R1 для
восстановления его первоначального значения, которое было в стержневой
ветви. Поскольку в этот момент c(Rl) = 001734, управление возвращается
к этой ячейке, а именно к инструкции, которая следует непосредственно за
двумя директивами .WORD. И это именно то место, куда нужно вернуться.
Как и требовалось, при возврате в стержневую ветвь программы удалось
обойти эти две директивы .WORD. Как же это получилось? Это явилось ре-
зультатом использования режима адресации с автоувеличением в двух ин-
струкциях MOV в ячейках 002454 и 002456.
Хотя большая часть выполняемых здесь действий была существенной для
правильной обработки подпрограммы, тем не менее существовали и некото-
рые моменты, внешние по отношению к самим инструкциям подпрограммы.
Имеет смысл вкратце остановиться на них. Обратите внимание, что начальная
часть подпрограммы написана в расчете на то, что вызывающая подпрограм-
ма содержит два слова после обращения к подпрограмме, и должно быть по-
нятно, что если этих двух слов там нет или если они расположены в непра-
вильном порядке, то подпрограмма или будет работать неправильно, или во-
обще не сможет выполнить свою работу. Отметим затем, что подпрограмма
сохранила содержимое необходимых ей регистров до того, как начала рабо-
тать с ними. Поскольку подпрограмма не имеет достаточной информации об
активности регистров в стержневой ветви программы, которая ее вызывает,
то это представляется хорошей программистской практикой. И, наконец,
мы отмечали, что перед возвратом из подпрограммы было восстановлено со-
держимое регистров R0 и R2 из стека, однако умолчали о восстановлении со-
держимого регистра для перехода R1. Если бы c(R0) и c(R2) не были восста-
новлены перед выполнением инструкции RTS R1, то эта инструкция вытолк-
нула бы из стека верхний элемент в R1. Однако это было бы значением -из
стержневой ветви программы регистра R2, а не регистра R1. Невозможность
правильно восстановить значение R1 могла бы привести к серьезным по-
следствиям в стержневой ветви программы.
Если у читателя создалось впечатление, что при передаче аргументов под-
программе необходимо уделять много внимания деталям, то он не заблуж-
дается. Вызывающая программа должна располагать достаточной информа-
цией о том, как подпрограмма обрабатывает эти аргументы, а подпрограм-
ма — информацией о том, как она будет вызвана из стержневой ветви. Стек
166
001000
MATRIX! .BLKW 25.
001724 012746 000005 мои ♦5»-(SP) ;ПОМЕСТИТЬ СЧЕТЧИК И
001730 012746 001012 MOV 4MATRIX+12»- (SP)1 АДРЕС В СТЕК
001734 001740 004167 000504 JSR Rl.MAX JПЕРЕЙТИ НА ПОДПРОГРАММУ ((СЛЕДУЮЩАЯ ИНСТРУКЦИЯ ОСНОВНОЙ ПРОГРАММЫ)
(ПОДПРОГРАММА ’MAX’
002444 010046 МАХ: MOV RO.-(SP) (СОХРАНИТЬ РЕГИСТРЫ
002446 010746 MOV R2.-CSP) ( RO И R2 В СТЕКЕ
002450 016600 000010 MOV 10(SP)»R0 (ПОЛУЧИТЬ СЧЕТЧИК
002454 016602 000006 MOV 6(SP)»R2 J И АДРЕС 1-ГО ЧИСЛА
’СКЕЛЕТНАЯ' ПОДПРОГРАММА МАКСИМИЗАЦИИ
(C(R2) = АДРЕС 1-ГО ЧИСЛА
(C<RO) = СЧЕТЧИК ЧИСЕЛ» СРЕДИ КОТОРЫХ ИЩЕТСЯ МАКСИМАЛЬНОЕ
;МАКСИМУМ ВОЗВРАЩАЕТСЯ В R5
002460 005300 ВЕС RO :УМЕНЬШИТЬ HA 1 ЧИСЛО ; СРАВНЕНИИ
002462 012205 MOV (R2) + »R5 (l-E ЧИСЛО = ВРЕМ. МАКС.
002464 020522 LOOP: СМР R5,(R2) + (СРАВНИТЬ ВРЕМ. МАКС. С ЧИСЛОМ
002466 002002 BGE NEXT (ВРЕМ. МАКС. БОЛЬШЕ — ПРОПУСТИТЬ
002470 016205 MOV -2(R2)»R5 .ЗАМЕНИТЬ ВРЕМ. МАКС.
177776
002474 077005 NEXT: SOB RO .LOOP (ОБРАБОТАТЬ СЛЕДУЮЩЕЕ ЧИСЛО
002476 012602 MOV (SP1+.R2 (ВОССТАНОВИТЬ РЕГИСТРЫ
002500 012600 MOV (SPl+.RO ( R0 И R2 ИЗ СТЕКА
002502 012666 MOV (SP)+»2(SP) (ПЕРЕДВИНУТЬ R1 ИЗ ОСНОВНОЙ ПРОГРАММЫ
000002
002506 005726 TST (SP) + ( ВНИЗ СТЕКА И ПОДСТРОИТЬ SP
002510 000201 RTS Rl (СДЕЛАНО — ВЕРНУТЬСЯ
Рис. 8.8.3
должен вестись правильно, а при определении адреса возврата должны учиты-
ваться аргументы, передаваемые подпрограмме. В этом отношении програм-
мирование представляется довольно кропотливым, но, уделяя деталям доста-
точное внимание, можно получить правильно функционирующие полезные
подпрограммы.
Этот раздел мы завершим последним примером передачи аргументов
подпрограмме. В данном случае аргументы будут передаваться в стеке
(рис. 8.8.3). И, хотя это означает, что передача аргументов вполне стандарт-
ная, мы увидим, что потребуется более сложное программирование, чем в
двух рассмотренных ранее примерах. Идея проста: прежде чем передавать
управление подпрограмме, мы помещаем в стек счетчик чисел и адрес перво-
го числа.
MOV #5, —(SP)
MOV #MATRIX + 12, - (SP)
JSR R1,MAX
После входа в подпрограмму немедленно возникает трудность: два аргумен-
та, протолкнутые.в стек для передачи подпрограмме, не находятся больше на
167
-*—SP с (ML R2) *—SP
c(MLR0)
c(ML R1) с (ML R1)
MATRIX + 12 MATRIX + 12
5 5
Рис. 8.8.4 Рис. 8.8.5
вершине стека, откуда Их можно было бы вытолкнуть, поскольку инструк-
ция JSR протолкнула в стек текущее (из стержневой ветви) содержимое ре-
гистра для перехода R1. Поэтому, когда мы готовы выполнить подпрограм-
му МАХ, стек выглядит, как показано на рис. 8.8.4 (здесь ML Rl — значение
Rl из стержневой ветви, которое в нашем примере неизвестно). Фактически
же, поскольку мы решили, что хорошая практика состоит в том, чтобы под-
программа сохраняла (в стеке) содержимое любых регистров, которые ей
нужны, c(R0) и c(R2) опять будут протолкнуты в стек, и, таким образом,
счетчик чисел и адрес MATRIX +12 опустятся в стеке еще ниже (рис. 8,8.5).
Однако тот факт, что нужные нам числа погружаются в стек, не приводит к
заметным последствиям, поскольку любой элемент стека может быть доступ-
ным, хотя вытолкнуть можно только элемент, находящийся на вершине сте-
ка. Следовательно, например, счетчик чисел (5), расположенный на четыре
слова ниже вершины стека, может быть доступен с помощью" обращения
1O(SP). Таким образом, в подпрограмме МАХ инструкция MOV 1O(SP) ,R0
пересылает этот счетчик в R0, никоим образом, конечно, не воздействуя на
стек. Аналогично элемент MATRIX +12 пересылается в R2, и теперь можно
позволить подпрограмме продолжать работу, как и прежде.
Трудности возникают тогда, когда мы готовы вернуться в основную часть
программы. После восстановления первоначальных значений регистров R2 и
R0 из стержневой ветви программы стек содержит числа, показанные на рис.
8.8.6. В этом месте можно просто выполнить инструкцию RTS R1, и значе-
ние регистра R1 из стержневой ветви будет восстановлено правильно, а управ-
ление вернется к инструкции, которая следует за инструкцией J SR, вызыва-
ющей переход к МАХ. Но при этом в стеке все еще останутся два элемента,
MATRIX + 12 и 5. Хотя при некоторых обстоятельствах это может оказаться
желательным, обычно, когда аргументы передаются подпрограмме в стеке,
подпрограмма обязана ’’вычищать” стек до возврата управления и не остав-
лять эту работу основной программе. Это можно сделать с помощью двух ин-
струкций (рис. 8.8.7).
Первая — это MOV (SP)+, 2(SP), требующая небольшого анализа. Ссыл-
ка на (SP)+ есть ссылка на вершину стека, а именно на c(ML Rl) .После этой
ссылки, но перед ссылкой на 2(SP) содержимое указателя стека увеличива-
ется на 2. Таким образом, эта инструкция пересылает с (ML Rl) в ячейку,
расположенную на одно слово ниже новой вершины стека, и, следовательно,
с (ML Rl) пересылается в ячейку, которая перед этим была занята счетчиком
чисел (5). Таким образом, стек содержит два элемента — MATRIX + 12 на
168
вершине стека и c(ML Rl) на одно слово ниже. Следующая инструкция,
TST (SP)+, получает адрес в SP,увеличивает c(SP) на 2 и затем устанавлива-
ет коды условий N и Z в соответствии с содержимым этого адреса. Но в дей-
ствительности нас не интересует, как устанавливаются эти коды условий.
Назначение инструкции состоит не в проверке слова на вершине стека, а в
увеличении содержимого указателя стека на 2. Это — легкая однословная
схема для увеличения содержимого указателя стека (или любого другого ре-
гистра, если потребуется) на 2. Таким образом, нам удалось сместить указа-
тель стека вниз без того, чтобы выталкивать верхний элемент стека, посколь-
ку в действительности нам совершенно не нужно передвигать MATRIX +12
куда бы то ни было. Теперь стек содержит один элемент c(ML Rl), и ин-
струкция RTS R1 восстановит в R1 его зйачение из стержневой ветви про-
граммы.
Читатель может здесь обвинить нас в ’’плохом обращении” со стеком, по-
скольку мы манипулировали стеком и указателем стека способами, наруша-
ющими ’’правила” ведения стека, если под этими правилами понимать, что
элементы добавляются и удаляются только на вершине стека и что указатель
стека изменяется только при проталкиваниях и выталкиваниях. Возразим на
это, что в конце концов цель оправдывает средства. Хотя потребовалось
определенное внимание, эти инструкции все-таки выполнили нужную нам ра-
боту. (Как сказал бы Шалтай-Болтай: ’’Вопрос в том, кто хозяин положе-
ния — и в этом все”.)
Обратите внимание, что роль, которую играет регистр для перехода R1, в
этом примере значительно менее активная, чем в предыдущем случае. Здесь
R1 был использован только для сохранения адреса возврата, тогда как аргу-
менты проходили через стек. В предыдущем примере R1 использовался не-
посредственно для получения этих аргументов из стержневой ветви про-
граммы.
8.9. ИСПОЛЬЗОВАНИЕ PC В КАЧЕСТВЕ РЕГИСТРА ДЛЯ ПЕРЕХОДА К
ПОДПРОГРАММЕ
До сих пор в примерах в качестве регистра для перехода при обращении к
подпрограмме мы использовали один из регистров общего назначения — от
R0 до R5. Однако не видно причин, почему бы и PC не мог быть использован
для этой цели, в чем мы и убедимся. Вспомним, что действие инструкции
JSR (см. с. 158) состоит в вычислении адреса приемника, помещения в стек
169
содержимого регистра для перехода, в придании этому регистру текущего
значения PC и в передаче затем управления подпрограмме. Если регистром
для перехода является PC, то действие (в виде диаграммы) выглядит так:
TEMP *- адрес приемника
HSP)^c(PC)
PC*-с (PC)
PC*-с (TEMP)
Третий шаг этого процесса кажется потерянным ходом, но ключевым дей-
ствием является второй шаг — помещение в стек текущего содержимого PC,
представляющего собой адрес инструкции, следующей за JSR. Аналогично,
инструкция RTS PC соответственно восстанавливает PC для возврата в стерж-
невую ветвь программы (см. с. 158).
РС*-с(РС)
PC*-(SP)t
Вспоминаем, что, когда мы передавали аргументы подпрограмме с помо-
щью директив .WORD, непосредственно следующих за обращением к подпро-
грамме, и когда регистром для перехода был один из регистров общего наз-
начения, к моменту передачи управления подпрограмме регистр для перехо-
да содержал адрес первого из аргументов. Мы уже убедились в легкости, с
которой могут быть получены аргументы в этом случае, и именно поэтому
аппаратная инструкция JSR сконструирована так, как есть, — она значитель-
но упрощает передачу аргументов подпрограмме. Если же, с другой стороны,
регистром для перехода является PC, то ситуация не кажется столь же удоб-
ной, поскольку адрес первого аргумента находится на вершине стека и до-
ступ подпрограммы к аргументам в этом случае более обременителен. Если,
например, стержневая ветвь передает таким образом три аргумента подпро-
грамме, вызванной через PC, то мы должны гарантировать подстройку чис-
ла на вершине стека, прибавляя в этом случае 6, до возврата из подпрограм-
мы с помощью RTS PC.
Если стержневая ветвь программы передает аргументы подпрограмме или
непосредственно через регистры общего назначения или в стеке, то использо-
вание PC в качестве регистра для перехода приводит к ситуации, управлять
которой так же легко, как и в случае регистра общего назначения. Фактичес-
ки применение PC для этих целей освобождает дополнительные регистры для
использования их в подпрограмме. Читатель может быть удовлетворен, если
пересмотрит три примера из предыдущего раздела, заменяя в каждом случае
регистр для перехода R1 на PC и определяя, какие модификации необходимо
сделать (если они вообще потребуются).
8.10. РЕКУРСИВНЫЕ ПОДПРОГРАММЫ
В разд. 8.7 мы видели, что подпрограммы в процессе своего выполнения
могут обращаться к другим подпрограммам, и даже если несколько таких
вложенных подпрограмм используют один и тот же регистр для перехода,
стековая структура гарантирует правильные и автоматические возвраты из
подпрограмм. Фактически же где-то в середине своей работы подпрограмма
может обращаться к самой себе. Эта концепция известна как рекурсивное
170
1 ;ОСНОВНАЯ ПРОГРАММА — ВЫЗЫВАЕТ ПОДПРОГРАММУ ’ODD’.
2 : ЧТОБЫ НАЙТИ ПОЗИЮШ ПЕРВОГО «ЧЕТНОГО БАЙТА В
3 1 БЛОКЕ ПАМЯТИ
4 1
5 000000 012701 START* MOV SBLDCK.Rl ОСТАНОВИТЬ АДРЕС 1-ГО ЧИСЛА
-000012’
6 000004 004767 JSR PC.ODD fВЫЗВАТЬ ПОДПРОГРАММУ
000016
7 000010 000000 HALT JCTOB — Rl СОДЕРЖИТ
9 ; АДРЕС 1-ГО нечетного
9 ; БАЙТА-В БЛОКЕ ЧИСЕЛ
10 I
11 (БЛОК ЧИСЕЛ
12 I
13 000012 000212 BLOCK: .HORD 212.300.43552.177722.3.24
000014 000300
000016 043552
000020 177722
000022 000003
000024 000024
14 i
15 ПОДПРОГРАММА ’ODD*
16 5
17 000026 132711 ODD* ВИВ И* (RD 'ПРОВЕРИТЬ БИТ 0 БАЙТА
000001
19 000032 001003 BNE DOME .ЕСЛИ НЕ НУЛЕВОЙ. ТО БАЙТ НЕЧЕТНЫЙ,
19 000034 005201 INC Rl 1 ИНАЧЕ УВЕЛИЧИТЬ АДРЕС НА 1
20 000036 004767 JSR PC.ODD : И ВЫЗВАТЬ ЭТУ ЖЕ ПОДПРОГРАММУ
177764
21 1
22 000042 000207 DONE: RT8 PC 1ВЕРНУТЬСЯ
23 1
24 000000’ .END START
Рис. 8.10.1
обращение к подпрограмме. При построении подобной подпрограммы необ-
ходимо уделять внимание тому, чтобы избежать бесконечной рекурсии - яв-
ления, при котором подпрограмма обращается к самой себе постоянно без
какой-либо возможности завершить рекурсию. Стек при этом опять обраба-
тывает возвраты таким способом, что требуется лишь небольшое вмешатель-
ство программиста.
В качестве простого и, возможно, полезного примера рассмотрим ситуа-
цию, при которой необходимо узнать адрес в блоке памяти, где встречается
первый байт, содержащий нечетное (восьмибитовое) число. Подпрограмма
полагает, что адрес первого такого байта, подлежащего проверке, находится
в R1, и поскольку никаких других аргументов передавать не нужно, в каче-
стве регистра для перехода к подпрограмме используется PC. Первой ин-
струкцией подпрограммы ODD является BITB #1, (R1), выполняющая логи-
ческую операцию И над числом 001 и содержимым байта, адрес которого на-
ходится в R1. Устанавливаются коды условий, и если результат операции И
был не нулевым, то, очевидно, бит 0 байта был равен 1, и, следовательно,
Содержимое этого байта нечетное. Листинг подпрограммы ODD и образца
стержневой ветви программы, вызывающей ее, показан на рис. 8.10.1.
Прослеживая логику этой программы, читатель столкнется с некоторыми
трудностями, но будет очень полезно понаблюдать за стеком каждый раз,
когда изменяется его состояние в результате выполнения инструкций JSR и
RTS, и в особенности обратить внимание на то, каким образом возвраты на-
чинают ’’распутывать” стек, как только будет найден байт, содержащий не-
четное значение.
Хотя приведенный пример несколько надуманный, концепция рекурсии
171
ни в коей мере не является чем-то необычным в вычислительной технике и
математике. Нет ничего необычного в том, когда математические функции
определяются рекурсивно, и мы приведем один такой известный пример.
Пусть п — неотрицательное целое; определим
функцию/следующим образом:
/(и) = 1, если п = О
/(и) = п •/(« — !), если п > О
Читатель, по-видимому, узнал здесь определение функции факториала, кото-
рая обычно записывается каки!. (Чтобы установить, что это действительно
определение /(и) для всех неотрицательных целых и, необходимо использо-
вать такой метод, как математическая индукция.) Рекурсия, конечно, заклю-
чена во второй строке определения, где функция вызывает саму себя. Дей-
ствие здесь аналогично тому, что имело место в последнем примере; в упраж-
нениях мы дадим некоторые предложения по поводу написания программы
для вычисления и! с помощью рекурсивных обращений к подпрограмме. По-
добные рекурсивные определения зачастую приводят к рекурсивным про-
цедурам при программировании. В оставшейся части книги мы увидим и дру-
гие примеры.
8.11. ПОДПРОГРАММЫ С НЕСКОЛЬКИМИ ТОЧКАМИ ВХОДА
Ячейка памяти, в которую процессор передает управление из стержневой
ветви программы, выполняя переход к подпрограмме, называется точкой
входа подпрограммы — точкой, через которую осуществляется вход в под-
программу. В двух основных примерах подпрограмм в данной главе этим
точкам входа были даны символьные имена PRINT и МАХ. Однако подпро-
грамма не обязана иметь лишь единственную точку входа; вполне возможно
и, вероятно, полезно входить в подпрограмму в нескольких местах.
В качестве простого (хотя и бесполезного) примера этой идеи рассмотрим
подпрограмму, которая выполняет несколько действий: при входе в нее в
соответствующем месте она может уменьшить c(R4) на 6, на 4 или на 2:
DECBY6: SUB #2,R4
DECBY4: SUB #2,R4
DECBY2: SUB #2,R4
RTS PC
Поскольку никаких аргументов передавать не надо, мы полагаем, что регист-
ром для перехода к подпрограмме является PC. Очевидно, что если вход в
подпрограмму осуществляется в точке по имени DECBY6, то будут выпол-
няться три вычитания числа 2, что дает полное уменьшение на 6. Действия при
входе в другие точки в равной степени очевидны.
В качестве более реалистического примера рассмотрим опять подпрограм-
му МАХ, которая возвращает в R5 наибольшее из указанного набора чисел.
В частности, нас будет интересовать версия на рис. 8.8.2, в которой аргумен-
ты — счетчик чисел и адрес первого числа — передаются подпрограмме с по-
мощью директив .WORD, непосредственно следующих за обращением к под-
программе. С этой процедурой читатель уже вполне освоился. Мы считаем
первое число максимумом (временным) и затем сравниваем его с остальны-
172
JПОДПРОГРАММА С ТОЧКАМИ ВХОДА ’МАХ’ И ’MIN’
; ВОЗВРАЩАЕТ В RS МАКСИМУМ ИЛИ МИНИМУМ ИЗ
t НАБОРА ЧИСЕЛ
002432 И 2767 000004 000027 МАХ: MOVB 0004»BRANCH*1 СФОРМИРОВАТЬ ВЕТВЛЕНИЕ ’B6E’
002440 000403 BR MAXMIN ; и пропустить инструкция
002442 112767 000007 000017 MINs MOVB 0007,BRANCH*1 СФОРМИРОВАТЬ ВЕТВЛЕНИЕ ’BLE’ 1 1ОСНОВНАЯ РАБОТА ПОДПРОГРАММЫ НАЧИНАЕТСЯ ЗДЕСЬ
002450 010046 MAXMIN: MOV RO,-(SP) СОХРАНИТЬ РЕГИСТРЫ
002452 010246 NOV R2»-(SP) ; R0 И R2 В СТЕКЕ
002454 012100 NOV <R1>*»RO .ПОЛУЧИТЬ СЧЕТЧИК
002456 012102 MOV <R1)+,R2 ; И АДРЕС 1-Г0 ЧИСЛА
002460 005300 DEC R0 СФОРМИРОВАТЬ ЧИСЛО СРАВНЕНИИ
002462 012205 NOV <R2)+,R5 ;1-Е ЧИСЛО МАКС. ИЛИ МИН.
002464 020522 LOOP: CMP R5»<R2)+ СРАВНИТЬ СО СЛЕД. ЧИСЛОМ
002466 000402 BRANCH। BR NEXT СТАНОВИТСЯ ’ВОЕ’ ИЛИ ’BLE’
002470 016205 177776 MOV -2(R2),R5 ; ЗАМЕНИТЬ НАКС. ИЛИ МИН.
002474 077005 NEXT l SOB R0»LOOP ;ОБРАБОТАТЬ СЛЕДУЮЩЕЕ ЧИСЛО
002476 012602 MOV <SP)*,R2 .ВОССТАНОВИТЬ РЕГИСТРЫ
002500 012600 MOV <SP)+,RO ; R0 И R2 ИЗ СТЕКА
002502 000201 RTS Rl ; И ВЕРНУТЬСЯ
Рис. 8.10.2
ми числами. Если обнаруживается большее число, то оно используется для за-
мены временного максимума в R5. Это решение принимается инструкцией
СМР, за которой следует инструкция BGE (ячейки 002464 и 002466 в листин-
ге на рис. 8.8.2). Очевидно, что если BGE заменить на BLE, то возвращаемое
в R5 число будет не максимальным, а минимальным из заданных чисел. Поэ-
тому мы намереваемся написать подпрограмму с двумя точками входа, на-
зываемыми МАХ и MIN, которая возвращает максимальное или минималь-
ное из заданных чисел в зависимости от того, к какой точке входа происхо-
дит обращение.
Поскольку единственным различием между двумя подпрограммами яв-
ляется инструкция условного ветвления — BGE или BLE, то мы принимаем
решение не дублировать код, необходимый для решения этих двух задач.
Вместо этого в каждой точке входа, МАХ и MIN, мылишь помещаем необ-
ходимые инструкции, гарантирующие правильный код инструкции ветвления
в ячейке 002466 до того, как будет выполняться тело подпрограммы. Обра-
тите внимание, что на рис. 8.10.2 в ячейку с символьным именем BRANCH
(002466) мы поместили исходный код инструкции BR NEXT. Это вовсе не
гарантирует того, что по нашему намерению должно в этом случае происхо-
дить. Эту инструкцию мы поместили, чтобы ассемблер вычислил правильное
пословное смещение в младшем байте инструкции ветвления. Старший байт
этого слова модифицируется в точках входа МАХ и MIN так, чтобы инструк-
ция стала соответственно BGE или BLE. Обычно мы стремимся избегать ин-
струкций, которые модифицируют другие инструкции, поскольку это зача-
стую приводит к таким программам, которые трудно читать, понимать и со-
провождать. Однако в данном случае легкость программирования, экономия
кода и получаемая в результате эффективность настолько велики, что мы не
можем противостоять искушению. Такой тип свободного программирования
ни в коей мере не противопоказан при условии, что он используется лишь
173
тогда, когда имеет смысл, и сопровождается хорошим документированием
исходного кода.
8.12. УПРАЖНЕНИЯ
8.1.1. Пусть LIST - (символьный) адрес первого числа в блоке из 29 последователь-
ных слов памяти, интерпретируемых программистом как вектор по имени V, индекси-
рованный следующим образом:
V(l), V(2),... , V(29) *
а) Напишите несколько инструкций для помещения содержимого V(I) в R5, где (це-
лый) индекс I предполагается находящимся в R1.
б) Определите, какие необходимы изменения, если индексирование начинается с 0 и
кончается на 28.
в) Определите, какие необходимы изменения, если индексирование начинается с -14
и заканчивается на 14.
8.1.2. Пусть ARRAY - это (символьный) адрес первого слова в блоке из 3010 после-
довательных слов памяти, которые интерпретируются программистом как матрица по
имени Т из пяти строк и шести столбцов, индексированная следующим образом:
Т(1, 1) ...Т(1,6)
Т (2, 1) . .. Т (2, 6)
Т (5, 1) .. . Т(5,6)
а) Напишите несколько инструкций для помещения содержимого Т (I, J) в R5, где
(целые) индексы I и J предполагаются находящимися соответственно в R1 и R2.
б) Определите, какие необходимы изменения, если индексирование начинается с
(-2, 0) и заканчивается на (2, 5).
8.1.3. Одна структура типа ’’первым пришел — первым вышел”, представляющая со-
бой некоторую модификацию очереди, называется ’’силосной башней”. В этом случае
голова ’’силосной башни”, откуда удаляются элементы (и которую лучше было бы наз-
вать дном ’’силосной башни”), всегда занимает фиксированную позицию. Когда элементы
добавляются, то они добавляются в хвост (лучше сказать, к вершине) ’’силосной баш-
ни”. Когда элементы удаляются, то все остальны элементы в ’’силосной башне” опуска-
ются на одну позицию. Эта структура показана на рис. 8.12.1. Объясните, почему линия
кассового обслуживания в универсаме лучше иллюстрирует эту структуру, нежели
очередь.
8.2.1. а) Если стек может содержать не больше 501О однословных элементов в один и
тот же момент времени, то какой минимальный объем памяти должен быть отведен для
него?
б) Предположим, что в программе требуются два стека, каждый из которых может
расти до 501О однословных элементов. Какой минимальный объем (комбинированной)
памяти должен быть отведен под эти стеки?
174
в) Предположим, что в программе требуются два стека и известно, что полное число
однословных элементов в обоих стеках в любой заданный момент времени никогда не
превосходит 5О1о. Какой минимальный объем (комбинированной) памяти должен быть
отведен под эти стеки? Почему? Как ведутся эти два стека?
8.2.2. Предположим, что стек, указателем которого является R3, первоначально со-
держит элементы, показанные на рис. 8.12.2. Определите, какие элементы окажутся в
стеке после выполнения каждой из следующих инструкций:
a) MOV (R3)+, R5 б) MOV #17, -(R3)
в) NEG (R3) г) ADD 4(R3), (R3)
Д) ADD (R3)+, 2(R3) е) MOV (R3)+,(R3)
ж) MOV (R3), ~(R3)
8.2.3. Предположим, что в программе используется очередь, и мы оцениваем, что она
никогда не может содержать одновременно больше, чем 601о слов. Предположим, так-
же, что область очереди в программе устанавливается следующим образом:
QUEUE: .BLKW 74
QUEND:
а) Как должны инициализироваться указатели на голову и хвост очереди?
б) Как можно в любой заданный момент времени определить, ну сто очередь или пол-
на!
в) Предположим, что после инициализации указателей на голову и хвост очереди в
нее добавлено 4010 элементов. Затем эти же самые элементы удаляются из очереди. Те-
перь очередь совершенно пуста. Какими оказываются состояния указателей на голову и
хвост очереди? Можно ли добавить к очереди еще 4010 элементов?
8.2.4. Читатель, детально разобравшийся в упражнении 8.2.3, найдет, что управление
очередью, без сомнения, тривиальный вопрос. Ведение же стека представляется более
сложным. В качестве задачи промежуточной трудности объясните, как нужно обходить-
ся с "силосной башней" (описание ее смотрите в упражнении 8.1.3).
8.3.1. Предположим, что в программе требуется стек, который по оценкам может со-
держать максимум 60,0 (однословных) элементов. Указатель стека получает первона-
чальное значение STKEND, а пространство под стек резервируется следующим образом:
STACK: .BLKW 74
STKEND:
а) Как программист может определить, что стек полон и что еще одно проталкивание
приведет к его переполнению?
б) Как программист может определить, что стек пуст, так что дополнительное вытал-
кивание приведет к его исчерпанию?
8.3.2. Один способ обойти проблему использования стека для удержания как слов,
так и байтов состоит в том, чтобы считать стек байтовым. Тогда для проталкивания сло-
ва потребуются два проталкивания, по одному на каждый байт слова. Выталкивание
слова будет трактоваться аналогично.
а) Если в качестве указателя на такой стек используется R2, то какие инструкции
потребуются для проталкивания в стек содержимого слова, имеющего символьный ад-
рес DATA?
б) Что потребуется сделать для выталкивания верхнего элемента стека в слово с
символьным адресом ADDR?
в) Как можно протолкнуть в стек содержимое R4 (как слово) ?
8.4.1. Предположим, что в программе на рис. 8.4.2 счетчик (4) проталкивается в стек
[MOV #4, -(SP)], а не помещается в R0 [MOV #4, R0]. Какие потребуются изменения
программы?
8.5.1. Передавая управление подпрограмме, мы вместо того, чтобы сохранять теку-
щее содержимое PC в регистре общего назначения, сохраняем его в стеке. Тогда при воз-
175
враге из подпрограммы требуется подстройка этого элемента стека. Покажите, что сле-
дующая схема обеспечивает правильный возврат к основной части программы:
MOV PC, -(SP)
JMP SUBR
SUBR:
ADD #4, (SP)
MOV (SP)+, PC
8.5.2. Какие изменения необходимо внести в схему из упражнения 8.5.1, если пере-
• ход к подпрограмме осуществляется с помощью следующих инструкций:
a) MOV MOV JMP #SUBR, R2 PC, -(SP) (R2) 6) MOV MOV JMP PC, -(SP) #SUBR, R2 (R2)
в) MOV PC, -(SP) r) MOV PC, -(SP)
MOV #SUBR, -(SP) MOV #SUBR, -(SP)
JMP @(SP) JMP @(SP)+
Д) MOV #SUBR-4, R2 -
MOV PC, -(SP)
JMP 4(R2)
8.5.3. Рассмотрим две подпрограммы', SUBR1 и SUBR2, такие, что инструкции в
SUBR1 включают переход к SUBR2, причем каждая подпрограмма использует R1 для
удержания текущего значения PC (адреса возврата) .
MOV Rl, -(SP)
MOV PC, Rl
ADD #10, Rl
JMP SUBR1
MOV (SP)+,R1
SUBR1:
MOV Rl, -(SP)
MOV PC, Rl
ADD #10, Rl
JMP SUBR 2
MOV (SP)+,R1
MOV Rl, PC
176
SUBR2:
MOV R1,PC
Внимательно прослеживая элементы стека и значения в R1, покажите, что SUBR2 пра-
вильно осуществляет возврат к SUBR1, которая, в свою очередь, правильно выполняет
возврат в основную часть программы. Что можно сказать об этой схеме, почему она
представляется функционирующей правильно?
8.6.1. Предположим, что SUBR - подпрограмма, использующая R3 в качестве реги-
стра для .перехода. Самый простой способ обращения к этой подпрограмме из основной
части программы состоит в использовании инструкции JSR R3, SUBR или, возможно,
JSR R3, @#SUBR. Однако другой возможностью может быть
MOV #SUBR, R5
JSR R3, (R5)
(Это не столь курьезно, как может показаться вначале; подобные конструкции, исполь-
зующие косвенный регистровый режим, не столь уж необычны.) Проверьте, что эта ин-
струкция JSR вместе с инструкцией возврата RTS R3 приведет к желаемому результату.
Но рассмотрим теперь несколько более причудливую конструкцию; i
MOV #SUBR,R3 ' '
JSR R3, (R3)
Прослеживая детали инструкций JSR и RTS, определите, произойдет ли правильный пе-
реход к подпрограмме. Если да, то приведет ли инструкция RTS R3 (в SUBR) к правиль-
ному возврату к основной части программы? Если да, то каким будет содержимое R3 к
моменту возврата управления основной программе?
8.6.2. Определите, может ли PC использоваться в качестве регистра для перехода к
подпрограмме, проследив детали программного сегмента
002066 JSR PC, SUBR
002072
003644 SUBR:
004042 RTS PC
6
8.6.3. Мы утверждали, что инструкция RTS Rn придает PC текущее значение Rn, а
верхний элемент стека выталкивается в Rn. Фактически ЦП выполняет ’’инструкции’
MOV Rn, PC
MOV (SP)+, Rn
Покажите, что инструкция RTS Rn не может быть заменена программными инструк-
циями
MOV Rn, PC
MOV (SP)+, Rn
177
или программными инструкциями
MOV (SP)+, Rn
MOV Rn, PC
8.6.4. Предположим, что SUBR - это подпрограмма, возврат из которой осуществля-
ется с помощью инструкции RTS R4. Каковы будут последствия обращения к этой под-
программе с помощью инструкции JSR Rl, SUBR?
8.6.5. Предположим, что вместо инструкции. JSR, которую мы описали, в ЭВМ PDP-11
реализована аппаратная инструкция, которой мы присвоим временную мнемонику
SPJS (Save-Program-counter-and-Jump-to-Subroutine - сохранить-программный-счетчик-и-
перейти-к-подпрограмме). Она ведет себя следующим образом. Когда выполняется ин-
струкция SPJS SUBR, текущее содержимое PC сохраняется в SUBR (т. е. в слове с сим-
вольным адресом SUBR) и управление передается в ячейку SUBR + 2 - PC <- SUBR + 2.
Тогда типичное обращение к подпрограмме выглядело бы так:
SPJS SUBR
(следующая инструкция стержневой ветви)
SUBR: .BLKW1 ; АДРЕС ВОЗВРАТА СОХРАНЯЕТСЯ ЗДЕСЬ
(первая инструкция подпрограммы SUBR)
(Это не просто надуманное упражнение. В некоторых ЭВМ используется именно эта схе-
ма перехода к подпрограммам.)
а) Покажите, что при этой схеме управление будет эффективно передаваться подпро-
грамме, а адрес возврата будет сохраняться.
б) Покажите, что для возврата из подпрограммы не нужна специальная инструкция
типа RTS, указав стандартную инструкцию ЭВМ PDP-11, с помощью которой можно осу-
ществить возврат в основную часть программы.
в) Покажите, что эта инструкция будет правильно обрабатывать вложенные обраще-
ния к подпрограммам и, соответственно, вложенные возвраты, как в упражнении 8.5.3.
8.8.1. Рассмотрим .следующее обращение к подпрограмме:
026040 ADDR1: .WORD 6
026042 ADDR2: .WORD 142
030662
030666
030670
030672
JSR R4, SUBR
.WORD ADDR1
.WORD ADDR2
(следующая инструкция
стержневой ветви)
Показанная ниже подпрограмма SUBR не делает ничего, кроме установки значений од-
ного или нескольких регистров от R0 до R3, и затем осуществляет возврат к стержне-
вой ветви программы. Для каждого случая укажите, какие значения придаются регист-
рам, и будет ли правильно осуществляться возврат к следующей инструкции в стержне-
вой ветви. Если возврат неправильный, предложите шаги, которые могут гарантировать
правильный возврат.
a) SUBR: MOV (R4)+,R0 б) SUBR: MOV fe(R4)+,R0
MOV (R4)+,R1 MOV @(R4)+,R1
RTS R4 RTS R4
в) SUBR: MOV (R4), R0 r) SUBR: MOV @(R4)+,R0
MOV @(R4)+,R1 MOV @(R4)+,R1
17o MOV R4, R2 MOV @-2(R4), R3
MOV (R4)+ ,R3 RTS R4
RTS R4
д) SUBR: MOV (R4)+,R0 e) SUBR: MOV (R4), R0
MOV @-(R4), Rl MOV @(R4)+,R1
MOV @(R4)+, R2 RTS R4
MOV @-2(R4), R3
RTS R4
ж) SUBR: MOV @(R4), R0
MOV (R4), Rl
TST (R4)+
MOV @(R4)+, R2
RTS R4
8.8.2. Модифицируйте программу на рис. 8.8.3 так, чтобы после возврата в основную
часть программы максимум оказался не в регистре R5, а на вершине стека (конечно,
этот максимум должен быть в данном случае единственным элементом стека).
8.8.3. Можно ли в программе на рис. 8.8.3 инструкцию TST (SP)+ в ячейке 002506 за-
менить на TSTB (SP)+? Почему да или почему нет?
8.8.4. Обсуждая программу на рис. 8.8.3, мы утверждали, что с помощью инструкции
TST (SP)+ можно легко увеличить содержимое SP на 2. Мы также указывали, что эта же
схема может использоваться для увеличения на 2 содержимого регистра общего назначе-
ния. Покажите, что это не всегда так, т. е. определите условия, при которых TST (Rn) +
будет увеличивать Rn на 2 (п = 0,1,..., 5), и условия, прй которых эта инструкция не
подходит для этой цели. Почему всегда эта схема работает с R6 (SP)?
8.9.1. Мы видели, что PC (R7) может использоваться в качестве регистра для перехо-
да к подпрограмме. Каким будет эффект использования SP (R6) для этой цели? Будет
ли этот прием действенным всегда? Иногда? Или никогда?
8.9.2. Мы говорили, что использование PC в качестве регистра для перехода к под-
программе иногда оказывается неудобным, если аргументы, необходимые подпрограм-
ме, передаются с помощью слов, следующих за обращением к подпрограмме. Рассмот-
рим следующий конкретный пример:
JSR PC, SUBR
.WORD ARGO
.WORD ARG1
.WORD ARG2
.WORD ARG3
.WORD ARG4
.WORD ARG5
(следующая инструкция стержневой ветви)
Покажите, какие инструкции нужны в подпрограмме SUBR для придания Rn значения
ARGn(n “ 0» 1,... ,5), чтобы инструкция возврата (RTS PC) могла правильно вернуть
управление к следующей инструкции стержневой ветви программы.
8.9.3. Один интересный способ передачи аргументов подпрограмме (используемый,
например, некоторыми компиляторами Фортрана) заключается в следующем:
JSR PC, SUBR
BR NEXT
.WORD ARG1
.WORD ARG2
.WORD ARGK
NEXT: (Следующая инструкция стержневой ветви)
179
а) Покажите, что возврат из подпрограммы выполняется правильно без подстройки
регистра, использованного для перехода.
б) Покажите, как подпрограмма SUBR может осуществить доступ к i-му аргументу,
ARGi.
в) Какое ограничение налагает эта схема на число аргументов, которые могут быть
переданы подпрограмме?
г) Существует дополнительный и полезный аспект этого специфического метода пе-
редачи аргументов. Одна из проблем, присущих обращениям к подпрограммам, заклю-
чается в том, что обычно подпрограмма ожидает передачи конкретного числа аргумен-
тов. В общем случае, если число аргументов, передаваемых из стержневой ветви под-
программе, отличается от ожидаемого, то не только вероятна неправильная работа под-
программы, но и возможны проблемы с возвратом из нее. Покажите, что в данном слу-
чае проблемы с возвратом никогда не возникают. Покажите также, что подпрограмма
может определить, сколько аргументов было ей передано, и, таким образом, сможет
предпринять корректирующие действия, если число отличается от ожидаемого. И, нако-
нец, в качестве следствия покажите, что если аргументы передаются предложенным спосо-
бом, то можно написать подпрограмму, обрабатывающую переменное.число аргументов.
8.9.4. Другой метод передачи аргументов (также используемый некоторыми компи-
ляторами Фортрана) такой: TABLE: .WORD n .WORD ARGI .WORD ARG2
WORD ARGn
MOV #TABLE, R5
JSR PC, SUBR
Покажите, как подпрограмма может определить число переданных ей аргументов и как
она может осуществить доступ к f-му аргументу, ARGi.
8.9.5. Если подпрограмме требуются аргументы из стержневой ветви программы и
она использует PC в качестве регистра для перехода, то аргументы удобно поместить в
стек, чтобы подпрограмма могла их оттуда потом извлечь. Например, если перед выпол-
нением инструкции JSR в стек помещаются (в указанном здесь порядке) два аргумента,
ARG1 и ARG2, то стек будет выглядеть' так, как показано на рис. 8.12.3, и к аргументу
ARG1 можно обращаться с помощью 4 (SP), а к аргументу ARG2 — с помощью 2 (SP).
Хотя все это делается вполне просто, после возврата из подпрограммы (с помощью RTS
PC) аргументы все еще будут находиться в стеке. И если мы хотим, чтобы подпрограм-
ма вычищала стек перед возвратом, то возникает проблема, хотя и разрешимая. В неко-
торых моделях ЭВМ PDP-11 имеется инструкция с мнемоникой MARK, предназначенная
именно для очистки стека в момент возврата из подпрограммы.
Изучите описание инструкции MARK в приложении А, обратив
внимание на особую роль, которую играет R5, и затем объясните
в деталях вывод из программы, показанный на рис. 8.12.4, вклю-
чая и пояснение того, как выглядит стек на каждой стадии вы-
полнения программы. (Эта программа не претендует на то, что-
бы быть полезной, она лишь иллюстрирует действие инструкции
MARK. Но читатель может представить себе, как MARK может
использоваться для получения преимуществ в более практичес-
ки полезных ситуациях.)
180
1 001000 012705 000555 START: MOV *555»R5
2 001004 010546 MOV R5»-(SP> ;СОХРАНИТЬ C(R5) ИЗ ОСНОВНОЙ ПРОГРАММЫ
3 001006 012746 MOV *1234»-(SP) ;АРГУМЕНТ 1
001234
4 001012 012746 MOV *4321.-(SP) АРГУМЕНТ 2
004321
5 001016 012746 MOV *006402.-(SP) 5’MARK 2’
006402
6 001022 010605 MOV SP.R5
7 001024 004767 JSR PC.SUB
000044
В 001030 010567 MOV R5.ADDR
000036
9 001034 xOUT. OCT *ADDR.*1
10 001070 «EXIT
И
12 001072 ADDR: BLKW 1
13
14 001074 010667 SUB: MOV SP.ADDR
177772
15 001100 «OUT. OCT ADDR.*5
16 001134 000205 RTS R5 ;(HO HE ’RTS PC’)
17
18 001000’ .END STARTT
Рис. 8.12.4
8.10.1. Почему в рекурсивной подпрограмме на рис. 8.10.1 следующие инструкции
привели бы к неправильному результату?
ODD: BITB #1,(R1) +
BNE DONE
JSR PC, ODD
DONE: DEC Rl
RTS PC
8.10.2. Напишите подпрограмму ODD, приведенную на рис. 8.10.1, как нерекурсив-
ную (это сделать не труднее, чем написать рекурсивный вариант). Какими преимуще-
ствами или недостатками обладают обе эти версии?
8.10.3. Рассмотрим следующую подпрограмму:
FACT: CMP R2, #1 BEQ DONE MUL R2, Rl DEC R2 JSR PC, FACT
DONE: RTS PC
Покажите, что если перед обращением к FACT стержневая ветвь программы устанавли-
вает R1 в 1 и R2 в п, то FACT вернет в R1 значение п!. Какие ограничения налагаются на
п в этой процедуре? Как можно обойти или хотя бы ослабить эти ограничения? Как мож-
но написать эту подпрограмму нерекурсивно?
8.104. Рассмотрим следующее рекурсивное определение функции:
Пусть п — положительное целое; определим функцию F следующим образом:
F (и) = 1, если п = 1 или п — 2;
F (n) = F(n - 1) +F (п - 2), если п > 2.
F (п) называется n-м числом Фибоначчи. Напишите нерекурсивную подпрограмму для
генерации n-го числа Фибоначчи и проверьте, что эта подпрограмма вычисляет и распеча-
тывает (в десятичном представлении) первые 15 чисел Фибоначчи. Что потребуется для
написания этой подпрограммы в рекурсивном виде?
8.103. Покажите, что инструкция SPJS, описанная в упражнении 8.6.5, не может ис-
пользоваться для рекурсивного обращения к подпрограммам. Что потребовалось бы
181
сделать для выполнения рекурсии при таком типе инструкции обращения к подпро-
грамме?
8.1(L6. Как некоторый вариант концепции рекурсии рассмотрим две подпрограммы,
SUBR1 и SUBR2, каждая из которых обращается к другой, имея одни и те же средства
для завершения вложенных обращений к подпрограммам. Покажите, что и в данном
случае стек правильно справляется с обращениями и возвратами, рассмотрев пример, в
котором SUBR1 обращается к SUBR2, которая, в свою очередь, вызываегвиВВ!, и т. д.
Причем обращения завершаются и начинаются возвраты, например после того, как SUBR1
в третий раз обращается к SUBR2. (Такие подпрограммы называют иногда сопрограм-
мами.)
8.11.1. В упражнении 7.7.6 предлагается написать программу для размещения блока
чисел в возрастающем порядке.
а) Напишите эту задачу в виде подпрограммы, которой передаются два аргумента —
число чисел, подлежащих упорядочению, и адрес первого из них — с помощью директив
.WORD, следующих за обращением к подпрограмме.
б) Напишите подпрограмму так, чтобы аргументы передавались в стеке.
в) Напишите подпрограмму с двумя точками входа, INCR и DECR, такую, что в пер-
вой точке входа числа упорядочиваются в возрастающем порядке, а во втором случае -
в убывающем.
8.11.2. Другой метод размещения чисел в возрастающем или убывающем порядке на-
зывается линейной сортировкой методом вставок; он описан ниже для случая возраста-
ющего порядка. Следуя теме упражнения 8.11.1, напишите подпрограммы для реализа-
ции сортировок в возрастающем и убывающем порядках с помощью этого метода.
Рассмотрим набор чисел в последовательных ячейках памяти. Мы хотим переупоря-
дочить их так, чтобы они располагались в возрастающем порядке, и подходам к этой
задаче следующим образом. Временно считаем, что первое число занимает правильное
место, и проверяем второе число. Если второе число, по крайней мере, такое же боль-
шое, как и первое, мы оставляем все в том виде, как есть, и переходим к третьему чис-
лу. Если нет, то меняем местами первое и второе числа. Теперь решаем, куда поместить
третье число относительно первых двух чисел. Если третье число меньше первого, то мы
пересылаем второе число в ячейку, занимаемую третьим, пересылаем первое число во
вторую позицию и вставляем третье число в позицию первого. (Теперь три первых числа
размещены в возрастающем порядке.) Если третье число, по крайней мере, такое же
большое, как и первое, но меньше второго, мы меняем местами второе и третье числа.
Наконец, если третье число не меньше второго, то мы оставляем его там, где оно есть, и
переходим к четвертому.
В общем случае процедура сортировки выглядит следующим образом. Положим, что
первые fc чисел уже размещены в возрастающем порядке. Определяем место (к + 1)-го
числа среди первых к чисел. Предположим, что ему принадлежит i-я позиция. Числа меж-
ду 1-й и Л-й позициями включительно перемешаются вверх на одно слово и после этого
[к + 1)-е число вставляется в позицию, освободившуюся от/'-го числа. К моменту завер-
шения просмотра всех чисел они расположены в возрастающем порядке. (Хотя програм-
мирование такого типа сортировки несколько сложнее, чем в случае упражнения 8.11.1,
линейная сортировка методом вставок в общем случае представляется несколько более
эффективной, чем методом из упражнения 8.11.1.)
ГЛАВА 9. ВНЕШНИЕ СИМВОЛЫ, ПЕРЕМЕЩЕНИЕ И КОМПОНОВКА
9.1. ВНЕШНИЕ ПОДПРОГРАММЫ
В предыдущей главе мы рассмотрели ряд примеров подпрограмм, некото-
рые из которых были чисто иллюстративными, другие — ближе к реально ис-
пользуемым. Даже среди полезных подпрограмм некоторые были все^гаки в
182
большой степени специализированными. Например, подпрограмма для рас-
печатки пяти строк матрицы в истинном матричном формате, без сомнения,
была весьма полезна в программе, манипулирующей матрицей размером 5 х
х 5, но трудно представить себе ее полезность в других программах. Но в то
же время подпрограммы для нахождения наибольшего или наименьшего из
набора чисел, или подпрограммы для сортировки чисел в возрастающем или
убывающем порядках предположительно могут оказаться полезными в ши-
роком диапазоне программ. Поэтому полезные подпрограммы мы можем
классифицировать как такие, которые имеют значение для конкретной про-
граммной среды, и такие, которые полезны в более общем случае. В этом
разделе мы вкратце изучим вторую категорию подпрограмм.
Для программиста подпрограмма общего назначения — это, без сомнения,
благо, особенно, если она хорошо написана, т. е. не зависит от программы, в
которой находится, аргументы передаются легко и т. п. Но наш энтузиазм по
поводу полезности подобных подпрограмм несколько уменьшается в связи с
тем, что мы должны включать исходный текст подпрограммы в любую про-
грамму, в которой требуется ее использование. По общему признанию нет
необходимости интересоваться логикой подпрограммы; мы просто копиру-
ем в программу исходный код, уверенные в том,* что этот исходный код бу-
дет ассемблирован в подпрограмму, работающую правильно (при условии,
что копирование выполнено без ошибок). Тем не менее, если такая подпро-
грамма весьма длинная или необходимы несколько подпрограмм, то задача
может стать обременительной, да и чем больший объем кода нужно копиро-
вать, тем больше вероятность ошибок человека.
Представляется, что есть два способа справиться с этой проблемой. Мы мо-
жем снабдить нашего ассемблирующего служащего копиями исходного кода
для всех полезных подпрограмм, затем, после представления ему основной
программы для ассемблирования, сказать ему, какие из этих подпрограмм
будут вызываться из основной программы, и потребовать ’’подключения”
исходного кода этих подпрограмм, например, в конец основной программы,
чтобы они лежали вне основного потока обработки. Можно предполагать, что
служащий справится с этой задачей. Обратите внимание, что при этом не устра-
няется воспроизведение исходного кода; мы просто переносим нагрузку с
программиста на ассемблер. Это один способ справиться с ситуацией и иногда
(хотя и нечасто) он используется на практике. Но мы заняты поиском чего-
либо более эффективного.
Предположим, что мы предоставляем нашему служащему копию исходно-
го кода одной \t3 таких полезных подпрограмм и просим его ассемблировать
ее и сохранить копию объектного кода, который он сгенерирует. Затем мы
даем ему исходный код основной программы и опять просим выполнить ас-
семблирование, но, помимо этого, еще и добавить к объектному коду основ-
ной программы объектный код подпрограммы, которая вызывается из стерж-
невой ветви. В этом случае служащему не надо каждый раз включать исход-
ный код подпрограммы в основную программу, а достаточно ассемблировать
подпрограмму только однажды-, повышение эффективности очевидно. Та-
кого рода подпрограмму называют внешней подпрограммой, поскольку ее
исходный код не является больше составной частью исходного кода основ-
ной программы.
183
Хотя эта схема и представляется желательной,она порождает столько проб-
лем, что они делают схему почти (но не совсем) неработоспособной. Чтобы
понять, что это за проблемы, рассмотрим простой пример. Мы пишем под-
программу, которая называется EXSUB (под этим мы понимаем, что точка
входа имеет символьный адрес EXSUB). Она ассемблируется, и ее исходный
код сохраняется (где-то). Затем мы пишем основную программу, содержа-
щую следующую инструкцию:
JSR R2, EXSUB
Такое обращение приведет к тому,что ассемблирующий служащий столкнет-
ся с проблемами, так как символ EXSUB в основной программе определен
не будет, поскольку EXSUB является внешней подпрограммой. Поэтому слу-
жащий вынужден будет отметить, что EXSUB является неопределенным сим-
волом. Но даже если мы заранее уведомим служащего, что EXSUB — это
внешняя подпрограмма, он все равно не будет в состоянии завершить работу
так, как это нам нужно. Вспоминаем, что объектный код подпрограммы
EXSUB должен добавляться к объектному коду основной программы. Даже
если служащему'удастся правильно идентифицировать набор объектного ко-
да, представляющего эту подпрограмму, он не сможет определить точку вхо-
да EXSUB, поскольку после ассемблирования подпрограммы он, как мы и
хотели, сохранил объектный код, но выбросил исходный код, а вместе с ним,
по-видимому, и таблицу символов для подпрограммы. Но ведь именно в таб-
лице символов и находится значение точки входа EXSUB. Наконец, даже если
нам удастся обойти все эти трудности, мы,очевидно, намного расширяем роль
служащего (ассемблера) по сравнению с первоначальными намерениями. По-
скольку ситуация представляется довольно сложной, а подробности могут
оказаться не вполне ясными, имеет смысл разобрать эти проблемы по очереди.
9.2. ВНЕШНИЕ СИМВОЛЫ
Из предыдущего обсуждения должно быть ясно следующее. Если ассемб-
лер не информирован заранее о том, что конкретный символ (в нашем при-
мере символ EXSUB) является внешним по отношению к основной програм-
ме, т. е. внешним символом, то ассемблер будет трактовать его как неопре-
деленный символ и помечать флагом ошибки любую строку исходного кода,
в которой есть ссылка на этот символ. Уведомить ассемблер мы можем с по-
мощью директивы .GLOBL — в нашем конкретном случае .GLOBL EXSUB.
Директива .GLOBL является аббревиатурой слова global (глобальный), кото-
рое в данном контексте имеет смысл, противоположный смыслу слова ло-
кальный. Локальный символ — это такой, который локален по отношению к
самой программе, т. е. определяется внутри программы. Поэтому глобаль-
ный символ — это такой, который не определяется локально. Мы предпочита-
ем использовать для этого понятия термин внешний (external) и предло-
жили бы даже использовать директиву .EXT, но директива .GLOBL уже име-
ется в ассемблере ЭВМ PDP-11.
Как же теперь, когда ассемблеру известно, что EXSUB определяется где-
то во вне, он будет обрабатывать такую ссылку, как JSR R2, EXSUB? Пред-
положим ради определенности, что это обращение к подпрограмме произво-
дится в ячейке 001044. Ассемблер может обработать часть JSR R2 этой ин-
184
струкции, ассемблировав ее как 004267. Следующее слово должно быть сме-
щением к ячейке памяти с символьным адресом EXSUB, но, конечно, ассемб-
лер не знает этого адреса и поэтому не может завершить ассемблирование ин-
струкции. Лучшее, что он может сделать, это распознать, что смещение долж-
но помещаться в следующем слове, и обеспечить возможность осуществле-
ния этого, т. е. оставить слово в объектном коде и подчеркнуть, что оно дол-
жно быть (как-то) заполнено позднее с помощью соответствующего смеще-
ния. Таким образом, здесь будет сгенерирован.следующий объектный код:
001044 004267 JSR R2, EXSUB
001046 000000G
001050 --------(следующая инструкция
стержневой ветви)
Обратите внимание, что в ячейку 001046 ассемблер поместил число 000000.
Кроме того, в листинге программы эта ячейка будет отмечена буквой G, по-
казывающей что содержимое этой ячейки определяется в результате глобаль-
ной (или внешней) ссылки. Поскольку ассемблер не в состоянии определить
значение символа EXSUB и поэтому не может вычислить правильное смеще-
ние для заполнения ячейки 001046, мы можем заключить, что ассемблер по-
мещает здесь значение 000000, только чтобы гарантировать резервирование
слова в объектном коде для смещения, и что само это число — 000000 — не
имеет никакого значения. Однако это будет правильно только отчасти. Ко-
нечно, ассемблер не может вычислить смещение, и слово должно быть остав-
лено в объектном коде. Но оставить ’’открытое” слово, значение которого не
приводит ни к каким последствиям,, ассемблер может более легко, просто
увеличив счетчик распределения ячеек на 2, как если бы встретилась дирек-
тива .BLKW 1.
Чтобы понять значимость числа, размещаемого ассемблером для неизвест-
ного смещения, мы должны предвидеть, что случится, когда модуль, содер-
жащий EXSUB, в конце концов будет добавлен к модулю основной програм-
мы (эта тема подробно разбирается в разд. 9.6). Когда впоследствии значе-
ние EXSUB становится известным (так что уже может быть вычислено пра-
вильное смещение), то смещение не просто помещается в ячейку 001046, а
прибавляется к содержимому этой ячейки. Конечно, в нашем примере ре-
зультат в обоих случаях был бы одинаковым, поскольку с (001046) =000000.
Но рассмотрим теперь следующий пример:
001044 004267 JSR R2, EXSUB -6
001046 177772
001050 -------(следующая инструкция
стержневой ветви)
Здесь ассемблер заполнил ячейку 001046 значением 177772 = —6. Когда в
конце концов становится известным значение EXSUB, смещение к EXSUB
вычисляется и прибавляется к содержимому 001046. Как мы и стремимся,
это дает смещение к EXSUB —6.
Несмотря на то, что EXSUB является внешним символом программы, он
появляется в таблице символов ассемблера в виде
EXSUB =***#**G
185
Как и следует ожидать, звездочки означают, что символ не был определен.
Буква G просто показывает, что символ был специфицирован как глобаль-
ный. Такой вход таблицы символов мы уже однажды встречали в листинге
на рис. 6.7.1, где символ $.... был показан как неопределенный и глобаль-
ный. Символ $....- это название точки входа внешней подпрограммы, вы-
зываемой макроинструкцией $OUT. ОСТ.
Нам удалось предотвратить выдачу диагностики ассемблером по поводу
неопределенных символов, но мы едва ли заметно продвинулись к нашей ко-
нечной цели — способности добавлять объектный код подпрограммы к ос-
новной программе таким образом, чтобы стержневая ветвь программы мог-
ла обращаться к этой внешней подпрограмме. Чтобы достичь успехов в этом
направлении, нам необходимо кое-что знать о среде, в которой происходит
ассемблирование и выполнение программ.
9.3. ОПЕРАЦИОННАЯ СИСТЕМА
В гл. 5 мы указывали на возможность загрузки в оперативную память ис-
полняемых машинных инструкций для программы, установки в PC стартово-
го адреса программы .и запуска выполнения машинного кода. Загрузка в
память может происходить с консольного терминала ЭВМ или с переключате-
лей передней панели на основе пословного ввода. Но теперь читатель должен
ясно понимать, что при тех размерах программ, с которыми мы имели дело
до сих пор (а они все-таки довольно малы по сравнению с обычными), эта
схема вряд ли может оказаться практически приемлемой. Вместо этого мы
используем возможности программы, написанной специально для данной це-
ли, называемой загрузчиком и являющейся одним сегментом из набора про-
грамм, известных как операционная система.
В более общих терминах можно сказать, что операционная система — это
набор программ, выполняющих полезные для программиста задачи с целью
не только обеспечить удобства программисту, но и достичь эффективного
использования всех аппаратных возможностей. Программист взаимодейству-
ет с этим набором служебных программ (утилит) с помощью стандартной
программы, обязанной обслуживать запросы и обеспечивать ведение всей си-
стемы. Такая программа обычно называется супервизором, монитором или
управляющей программой. Таким образом, когда, например, программист
хочет ассемблировать исходную программу, то делается запрос супервизор-
ной программе, задачей которой является передать управление соответству-
ющей утилите операционной системы, в данном случае, ассемблеру*. Анало-
гично, когда требуется прогон исполнимой на ЭВМ программы, то именно
операционная система следит за тем, чтобы машинный код был загружен в
оперативную память, а управление было передано в правильную ячейку памя-
ти. Обсуждая листинг программы на рис. 6.7.1, мы упоминали о том, что дей-
ствием макроинструкции $ЕХ1Т является возврат управления операционной
системе после завершения выполнения программы. Теперь мы знаем, что
управление должно быть возвращено операционной системе для того,чтобы
она смогла принимать другие команды программиста — для последующего
ассемблирования, выполнения или чего-либо еще.
В оставшейся части главы мы приведем некоторую информацию об от-
дельных модулях, составляющих операционную систему. Однако о самой
186
операционной системе — о супервизоре, о том, как он вызывает системные
утилиты и как они взаимодействуют между собой, мы ничего говорить не бу-
дем. Операционные системы предстают в таком разнообразии форм и разме-
ров, что было бы вряд ли полезно, да и возможно, описывать их здесь.
9.4. ОБЪЕКТНЫЕ ФАЙЛЫ И ВНЕШНИЕ СИМВОЛЫ
Назначение ассемблера состоит в построении объектной программы — на-
бора 16-битовых* слов, которые после загрузки в оперативную память могут
быть интерпретированы (декодированы) ЦП как исполняемые инструкции.
Но что должен делать ассемблер с объектным кодом, который он сгенериро-
вал? Ответ состоит в том, что этот код сохраняется (где-то) в файле, кото-
рый называется объектным файлом. Файл — это просто некоторый набор
взаимосвязанных данных. Объектный файл ассемблера — это такой сгенери-
рованный ассемблером набор 16-битовых слов, составляющих объектный
код, который соответствует данной исходной программе. Этот файл для об-
ращений к нему в будущем где-нибудь запоминается (или сохраняется) пос-
ле построения его ассемблером, работающим обычно совместо с другими
утилитами операционной системы (например, с файловым процессором).Где
запоминается объектный файл, в настоящий момент нас не интересует — в ти-
пичных современных вычислительных системах файлы сохраняются на маг-
нитных дисках или лентах, реальным является даже простое сохранение фай-
ла в оперативной памяти. Для нас существенно то, что файлу присваивается
имя, по которому позднее на него можно ссылаться. Имя само по себе не
столь уж важно, главное, чтобы оно однозначно определяло свой файл.
В разд. 9.7 мы увидим, какое значение придается имени, ассоциированному
с объектным файлом.
Мы повторяем, что функция ассемблера состоит в приеме в качестве вход-
ной информации исходной (или мнемонической) программы, генерации 16-
битового объектного кода для этой программы и сохранении этого объект-
ного кода в объектном файле под каким-то определенным именем. Но обра-
тите внимание на следующее. Как только программа ассемблирована, един-
ственное, что необходимо сохранять — это чистый объектный код — мнемо-
ники и символы программиста больше никак не используются, если только
исходная программа не содержит ссылки на внешний символ. В этом случае,
если такую ссылку предстоит когда-то разрешить, ассемблер должен сохра-
нить в объектном файле несколько больший объем информации. Рассмотрим
опять пример из разд. 9.2, в котором обращение JSR R2,EXSUB было ассемб-
лировано как
001044 004267
001046 000000
Очевидно, что нехорошо сохранять этот код без какой-либо информации о
том, как элемент 000000 в конце концов должен быть подстроен. Какая ин-
формация требуется для такой подстройки? Нам должно быть известно толь-
ко, где делается эта ссылка (001046), на что делается ссылка (EXSUB), и
как она делается — в этом случае ссылка представляет собой смещение от
текущего значения PC до точки входа EXSUB, хотя на EXSUB можно было
бы сослаться также по фактическому адресу. Поэтому, чтобы ссылка могла
187
быть разрешена позднее, вместе с чистым объектным кодом должна быть пе-
редана информация следующего вида:
EXSUB 001046 смещение
Конечно, если сделаны другие ссылки на EXSUB (или есть другие внешние
символы), то они должны быть включены, например
EXSUB 001046 смещение
001220 смещение
002004 адрес
002020 смещение
Таким образом, при построении объектного файла ассемблер должен
включать в него таблицу внешних ссылок, элементы которой аналогичны
показанным выше. Способ, каким конкретный ассемблер, работающий в дан-
ной операционной системе, сохраняет эту информацию о внешних ссылках,
может значительно варьироваться, но это — минимальная информация, кото-
рая должна пересылаться вместе с объектным кодом, хотя осуществляться
это может весьма специфически.
Еще одним доступным ассемблеру фрагментом информации, который мо-
жет потребоваться позднее, является стартовый адрес, указанный в директи-
ве .END — адрес, с которого начинается выполнение, если и когда выполняет-
ся программа на машинном языке. Эта информация передается в объектном
файле, который теперь представляется таким, как показано на рис. 9.4.1,
где мы произвольно выбрали для него имя MAIN.
Справившись с основной программой, перейдем теперь к подпрограмме
EXSUB, которая, как мы отмечали, должна подвергаться раздельному ас-
семблированию. Нет причин полагать, что ассемблер будет обрабатывать под-
программу иначе, чем основную программу, и фактически так оно и есть. Од-
нако есть один важный момент: как трактовать здесь стартовый адрес? С ка-
кого адреса мы хотим ’’запустить” выполнение подпрограммы? Вопрос этот
на самом деле бессмысленный, поскольку подпрограмма нигде не запускает-
ся — она обязана своим существованием основной программе, которая сама
будет запускаться и где-то передаст управление подпрограмме, а затем полу-
чит его назад, когда завершится выполнение подпрограммы. Поэтому поня-
тие стартового адреса к подпрограмме просто неприменимо и, следователь-
но, в директиву .END в конце исходного кода подпрограммы он не включа-
ется. Как ассемблер реагирует на эту ситуацию? Возможен целый ряд вариан-
тов, но обычно ассемблер назначает стартовый адрес по умолчанию, чтобы по-
казать, что фактически никакого стартового адреса нет. Самый легкий спо-
соб сделать это — дать подпрограмме стартовый адрес 000001, который в
силу нечетности никогда не может быть разрешенным. Почему же все-таки
ассемблер озабочен назначением стартового адреса подпрограмме? Есть две
причины, первой из которых является единообразие. Назначая стартовый ад-
рес каждому программному модулю^ который он ассемблирует, ассемблер
всегда порождает объектные файлы, содержащие этот адрес независимо от то-
го, является он допустимым или нет. Вторую причину мы обсудим в разд. 9.7.
Завершив обработку подпрограммы EXSUB и построив для нее объект-
ный файл, как показано на рис. 9.4.1, но с именем SUBR, ассемблер вновь
выбрасывает исходный код подпрограммы и таблицу символов. Но это губи-
Объектный файл
Стартовый адрес
ОбъектНый файл MAIN
Таблица
внешних ссылок
Таблица
глобальных
символов '
Объектный
(машинный) код
Рис. 9.4.1 Рис. 9.4.2
тельно для обращения из основной программы к EXSUB — точке входа в
подпрограмму, поскольку, если эта ссылка когда-либо будет разрешаться, и
мы уже позаботились о том, что ассемблер сохранил достаточную для ее раз-
решения информацию из стержневой ветви, то должно быть известно и значе-
ние EXSUB. Это значение хранится в таблице символов подпрограммы, и ес-
ли при завершении ассемблирования эта таблица выбрасывается, то мы теря-
ем какую-то жизненно важную информацию. Поэтому мы должны как-то
уведомить ассемблер, чтобы он сохранил достаточную информацию из табли-
цы символов, обеспечивающую возможность разрешения ссылки из стержне-
вой ветви на EXSUB. Иными словами, мы должны показать, что подпрограм-
мный символ EXSUB будет использоваться как глобальный, а не просто как
локальный внутри подпрограммы. Это можно сделать двумя способами. Пер-
вый включает использование директивы .GLOBL — .GLOBL EXSUB. Очевид-
но, что эта директива служит двойную службу. Если имя символа появляется
в директиве .GLOBL и если этот символ не определяется внутри программно-
го модуля, то очевидно, что символ глобальный в том смысле, что он внеш-
ний. Если символ определяется внутри модуля, то он глобальный в том смыс-
ле, что используется не просто локально, но и глобально. Такое двойное ис-
пользование директивы .GLOBL может приводить к конфузам, и по этой при-
чине мы предпочитаем второй метод декларации глобального символа: когда
символ определяется и используется как метка, он будет декларирован как
глобальный, если его определение сопровождается двумя двоеточиями вме-
сто одного:
EXSUB ::
Следствие декларации глобальных символов состоит в том, что ассемблер
при построении объектного файла включит в таблицу глобальных симво-
лов также эти символы и их значения. Объектный файл, построенный ассемб-
лером, показан на рис. 9.4.2 и представляет собой незначительное расшире-
ние файла, приведенного на рис. 9.4.1. Важно понимать, что ассемблер будет
генерировать эту структуру объектного файла независимо от типа програм-
много модуля, который он ассемблирует (стержневая ветвь или подпрограм-
ма) , поскольку подпрограмма может содержать внешние ссылки (например,
на другие внешние подпрограммы), а какие-то символы стержневой ветви
могут определяться как глобальные. Короче, ассемблер генерирует такие
189
188
файлы, как показано на рис. 9.4.2. И нет способа, с помощью которого ас-
семблер мог бы узнать, имеет ли он дело с модулем стержневой ветви, или с
модулем подпрограммы.
9.5. ПЕРЕМЕЩЕНИЕ ОБЪЕКТНОГО МОДУЛЯ
Когда мы впервые обсуждали концепцию ассемблирования мнемоничес-
кой программы, то видели, что если служащий возвращал нам листинг объек-
тного кода, который он ассемблировал, то по умолчанию этот модуль мог
быть загружен, начиная с адреса 000000. И если бы мы захотели загрузить
этот код в оперативную память, начиная, например, с ячейки 001000, то рис-
ковали бы нарваться на неприятности (см. с. 99). В частности, в примере
из разд. 6.5 мы имели
000004 .012702 MOV # DATA, R2
000006 000032
Объектный файл
000032 000403 DATA: 403
Служащий правильно поместил в ячейку 000006 адрес 000032 (DATA), но
если объектный код перемещается в 001000, то, конечно, символьный адрес
DATA перемещается вместе с ним, принимая значение 001032. Поэтому сло-
во (адрес) в ячейке 000006 не будет больше правильным и потребуются ка-
кие-то исправления перемещенного объектного кода перед тем, как он будет
выполняться. Мы видели, что ассемблер довольно легко может обнаружи-
вать, какие слова в объектном коде чувствительны к перемещению такого
сорта. Для удобства программиста он помечает подобные слова в листинге
программы апострофом (см. с. 105) и обозначает чувствительные к переме-
щению символы таблицы символов с помощью нотации R.
Чтобы понять, как может быть выполнено перемещение объектного кода,
необходимо ввести следующую терминологию. Адрес загрузки ассемблера
(АЗА) — это адрес, Назначенный ассемблером первому
слову объектного кода программного модуля. Физичес-
кий адрес загрузки (ФАЗ) - это адрес физической памя-
ти, в которую загружается первое слово объектного кода
программного модуля. (В нашем примере АЗА = 000000,
тогда как ФАЗ — 001000.) Проблема перемещения теперь
почти тривиальна. К любому слову, чувствительному к
перемещению, необходимо лишь прибавить число ФАЗ -
— АЗА. В нашем примере ФАЗ — АЗА - 001000 — 000000 -
= 001000, и, таким образом, при перемещении программы
слово 00032 в ячейке 000006 будет подстроено до 001032,
что в перемещенном модуле будет физическим адресом
первого из пяти чисел данных.
Однако, чтобы выполнить такое перемещение объект-
ного модуля, этот модуль должен нести два элемента
информации. Первый — это АЗА, позволяющий сделать
показанное выше вычисление (ФАЗ — АЗА) после приня-
Рис. 9.5.1 тия решения о том, в какое место физической памяти бу-
Стартовый адрес
Таблица
внешних ссылок
Таблица
глобальных символов
Адрес
загрузки ассемблера
Таблица
перемещения
Объектный
(машинный) код
дет загружаться модуль. А чтобы подправить чувствительные к перемещению
слова, мы, конечно, должны знать, какие это слова. Поэтому в объектный
модуль должна быть включена таблица, называемая таблицей перемещения,
которая показывает, какие слова нуждаются в подстройке при перемещении.
В конце концов, наш объектный файл содержит информацию, показанную
на рис. 9.5.1. Обратите внимание, что при перемещении объектного модуля
соответственно должен подстраиваться его стартовый адрес вместе со значе-
ниями символов в таблице глобальных символов, чувствительными к пере-
мещению.
9.6. ДОБАВЛЕНИЕ ОБЪЕКТНЫХ МОДУЛЕЙ
Теперь мы можем заняться проблемой, которая породила это довольно
длительное исследование, т. е. возможностью добавлять к объектному коду
основной программы объектный код подпрограммы таким образом, чтобы
ссылка из стержневой ветви на подпрограмму могда быть правильно уста-
новлена. Как пример различных процедур и вычислений используем подпро-
грамму на рис. 8.10.2, предназначенную для нахождения максимума или ми-
нимума из набора чисел. Эта подпрограмма ассемблируется как отдельный
программный модуль, который должен вызываться основной программой.
В качестве соответствующей стержневой ветви мы предлагаем пример (рис.
9.6.1), в котором просто читаются некоторые числа, находятся их максимум
и минимум, а затем они распечатываются.
Здесь нет ничего такого, чего бы мы уже не обсуждали, но несколько мо-
ментов заслуживают внимания. Заметьте, что в строке 3 символы МАХ и
MIN появляются в директиве .GLOBL. Это делается, чтобы показать, что они
являются внешними. В строке 9 (JSR Rl, MIN) ассемблер вставил код
ОСНОВНАЯ ПРОГРАММА
1 2 .TITLE ОСНОВНАЯ ПРОГРАММА
3 4 5 000000 BLOCK: .GLOBL .BLKW MAX,MIN ;ВНЕВНИЕ СИМВОЛЫ 40. S ПАМЯТЬ ДЛЯ 40. ЧИСЕЛ
6 000120 ANSWER: 7 .BLKW 2 ;MIN И МАХ ПОПАДАЮТ СЮДА
8 000124 BEGIN: «IN.DEC 0BL0CK.035. ?ПРОЧИТАТЬ 35. ЧИСЕЛ
9 000160 004167 JSR RbMIN ;НАЙТИ МИНИМУМ
OOOOOOG 10 000164 000043 . WORI 35. S ИЗ 35. ЧИСЕЛ,
И 000166 000000’ .WORD BLOCK ; НАЧИНАЮЩИХСЯ С ’BLOCK’
12 000170 010567 MOV R5,ANSWER ;СОХРАНИТЬ МИНИМУМ
177724 13 000174 004167 JSR R1»MAX ;НАЙТИ МАКСИМУМ
OOOOOOG 14 000200 000043 WORD 35. ; ИЗ 35. ЧИСЕЛ,
15 000202 000000’ WORD BLOCK ; НАЧИНАЮЩИХСЯ С ’BLOCK’
16 000204 010567 177712 MOV R5,ANSWER+2 ^СОХРАНИТЬ РЕЗУЛЬТАТ
17 000210 18 000244 19 20 000124’ ТАВЛИНА СИМВОЛОВ ANSWER 000120R BLOCK xOUT.DEC xEXIT .END OOOOOOR : #ANSWER,#2 .’РАСПЕЧАТАТЬ MIN И МАХ ; и выйти BEGIN S(’СТАРТОВЫЙ’ АДРЕС) MIN = ****** G
BEGIN 000124R MAX = ****** G к.... = ****** Q
Рис. 9.6.1
191
190
004167 в ячейку 000160. Это код инструкции JSR, но следующей слово было
ассемблировано как 000000G. Это, конечно, (неизвестное) смещение к MIN.
Аналогичная конструкция находится в строке 13. Обратите также внимание,
что в таблице символов символы МАХ и MIN появляются как неопределен-
ные (♦*****) и глобальные (G), хотя более подходящим термином, как
мы уже отмечали, было бы внешние.
В табл. 9.6.2 показано, что содержит объектный файл помимо самого
объектного кода (сравните с рис. 9.5.1). Подпрограмма, содержащая точки
входа МАХ и MIN, приведена на рис. 9.6.3; она также вполне понятна. Обра-
тите внимание на использование двойных двоеточий в строках 3 и 5, которые
определяют МАХ и MIN как глобальные символы. Заметьте, кроме того, что
в таблице символов они помечены и как глобальные, и как чувствительные к
перемещению. Информация, переносимая в объектном файле этого модуля,
показана в табл. 9.6.4.
Таблица 9.6.2
Таблица
Содержимое
Стартовый адрес 000124 (чувствительный к перемещению)
Внешние ссылки МАХ 000176 (используется как смешение)
MIN 000162 (используется как смещение)
Глобальные символы Отсутствуют
Адрес загрузки ассемблера 000000
Таблица перемещения 000166 (чувствительные к перемещению
000202 ссылки на BLOCK)
ПОДПРОГРАММА — НАХОДИТ МАХ ИЛИ MIN
1 2 .TITLE ПОДПРОГРАММА — НАХОДИТ MAX ИЛИ MIN
3 ОООООО 112767 000004 000027 МАХ:: MOVB 0004»BRANCH*1 'СФОРМИРОВАТЬ ВЕТВЛЕНИЕ 'BGE'
4 000006 000403 BR MAXMIN ; и ОБОЙТИ
5 000010 112767 000007 000017 MIN:: MOVB 9007»BRANCH*1 ?СФОРМИРОВАТЬ ВЕТВЛЕНИЕ 'BLE'
6 000016 010046 MAXMIN: MOV RO»-<SP) СОХРАНИТЬ РЕГИСТРЫ
7 000020 010246 MOV R2»-<SP) ; R0 И R2 В СТЕКЕ
В 000022 012100 MOV (R1)+,RO ;ПОЛУЧИТЬ СЧЕТЧИК
9 000,024 012102 MOV (R1)+»R2 ; И АДРЕС ПЕРВОГО ЧИСЛА
10 000026 005300 DEC R0 ;СФОРМИРОВАТЬ ЧИСЛО СРАВНЕНИИ
11 000030 012205 MOV <R2)+»R5 ?ПЕРВОЕ ЧИСЛО = МАХ ИЛИ MIN
12 000032 020522 LOOP: CMP R5,(R2>* ;СРАВНИТЬ СО СЛЕДУМИМ ЧИСЛОМ
13 000034 000402 BRANCH: BR NEXT ;СТАНОВИТСЯ 'BGE' ИЛИ 'BLE'
14 000036 016205 177776 MOV -2(R2)»R5 ;ЗАМЕНИТЬ МАХ ИЛИ MIN
15 000042 077005 NEXT: SOB R0.LOOP :ОБРАБОТАТЬ СЛЕДУИЕЕ ЧИСЛО
16 000044 012602 MOV (SP)+»R2 ВОССТАНОВИТЬ РЕГИСТРЫ
17 000046 012600 MOV <SP)+»RO ; R0 И К2‘ИЗ СТЕКА
18 19 20 000050 000201 000001 RTS .END Rl ; И ВЕРНУТЬСЯ ;('СТАРТОВОГО’ АДРЕСА НЕТ)
ТАБЛИЦА СИМВОЛОВ
BRANCH 000034R MAX OOOOOORG MIN 000010RG
LOOP 000032R MAXMIN 000016R NEXT 000042R
Рис. 9.6.3
Таблица 9.6.4
Таблица Содержимое
Стартовый адрес 000001 (чувствительный к перемещению)
Внешние ссылки Глобальные символы Отсутствуют МАХ 000000 (чувствительный к перемещению)
Адрес загрузки ассемблера Таблица перемещения MIN 000010 (чувствительный к перемещению) 000000 Отсутствует
Теперь мы располагаем всей информацией, необходимой, чтобы загрузить
основную программу в оперативную память, загрузить подпрограмму и под-
править объектный код основной программы в ячейках, ссылающихся на
МАХ и MIN, т. е. в ячейках 0Q0162 и 000176 в листинге основной программы
на рис. 9.6.1. Первое решение должно касаться ФАЗ для объектного кода ос-
новной программы. Годится любая физическая ячейка памяти и, поскольку
АЗА для этого модуля равен 000000, это значение представляется естествен-
ным выбором для ФАЗ, так как ФАЗ — АЗА = 0000000 и, следовательно, ни-
какого перемещения не требуется. Вместо этого, мы принимаем решение,
чтобы ФАЗ был равен 001000, по двум причинам. Во-первых, это наипростей-
ший случай, на котором полезно продемонстрировать нетривиальный ход
процесса перемещения. Во-вторых, адрес 001000 очень часто используется в
качестве ФАЗ в одной из популярных операционных систем, работающей на
многих вычислительных системах ЭВМ PDP-11. Таким образом, первое слово
объектного кода стержневой ветви будет загружаться в ячейку 001000, сле-
дующее - в ячейку 001002 и т. д., так что последнее слово этого модуля в
конце концов окажется загруженным в ячейку 001244.
Затем некоторые только что загруженные слова должны быть подстроены.
Во-первых, стартовым адресом этого модуля уже больше не является 000124 —
при перемещении он продвинулся до 001124. Из таблицы перемещения стерж-
невой ветви видно, что должны быть подстроены слова в ячейках 000166 и
000202, которые оказались после перемещения соответственно в ячейках
001166 и 001202. Для этого мы прибавляем к их содержимому ФАЗ - АЗА =
= 001000. В обоих случаях эти слова принимают значение 000000 + 001000 =
= 001000, т. е. значения для перемещения символа BLOCK. Итак, теперь
объектный код стержневой ветви загружен в оперативную память, и чувстви-
тельные к перемещению слова подправлены. Единственной задачей для стерж-
невой ветви осталось вычисление смещений к МАХ и MIN. Необходимость та-
ких вычислений следует из того, что в таблице внешних символов существу-
ют эти ссылки. Чтобы получить эти смещения, в память должен быть загру-
жен модуль, содержащий точки входа МАХ и MIN, после чего станут извест-
ны физические адреса МАХ и MIN.
Опять в качестве ФАЗ объектного кода подпрограммы годится любая
ячейка памяти (за исключением, конечно, тех ячеек, которые в настоящее
время заняты стержневой ветвью). Поскольку последняя ячейка, занятая
стержневой ветвью, имеет адрес 001244, представляется естественным на-
чать загрузку подпрограммы со следующей доступной позиции, а именно с
192
7 Зак. 2212
193
001246,' что мы и делаем. Мы замечаем, что в самой подпрограмме таблицы
перемещения нет (более правильно было бы сказать, что таблица перемеще-
ния пуста) и, следовательно, никаких подстроек объектного кода для пере-
мещения не требуется. Однако значения МАХ и MIN в таблице глобальны*
символов должны быть модифицированы. В частности, физические ячейки,
представляемые символами МАХ и MIN, теперь будут такие:
МАХ = 000000 + (ФАЗ - АЗА)
= 000000 + 001246 - 000000
= 001246
MIN = 000010 + (ФАЗ - АЗА)
= 000010 + 001246 - 000000
= 001256
Теперь мы можем внести ссылки на МАХ и MIN в стержневую ветвь програм-
мы. Из таблицы внешних ссылок видно, что ссылка HaMIN имеется в ячейке
000162, которая в результате перемещения стержневой ветви превратилась
теперь в 001162. К моменту, когда при выполнении программы ЦП извлечет
это (все еще неизвестное) смещение, PC будет иметь значение 001164. Поэто-
му помещаемое в 001162 смещение будет равно:
(значение MIN) — (текущее значение PC) + (текущее значение
содержимого 001162) = 001256 - 001164 + 000000 = 000072
Аналогично мы определяем, что смещение к МАХ при втором обращении
к подпрограмме должно быть равно 000046. Теперь, когда оба модуля за-
гружены в оперативную память и сделаны необходимые поправки, выполне-
ние может начаться со стартового адреса, а именно, с 001124.
Осталось разобрать еще два вопроса, хотя в действительности они лежат
несколько в стороне от нашего обсуждения. При загрузке объектного моду-
ля подпрограммы в оперативную память, начиная с ячейки 001246, ничего не
упоминалось о подстройке стартового адреса подпрограммы, 000001. По-
скольку все модули обрабатываются единообразно, мы подстроим и его, что
дает 000001 + 001246 = 001247. Из-за нечетности результата он по-прежнему
остается недействительным. Как мы увидим, этот адрес никак не влияет на
окончательное выполнение программы, но нельзя сказать, что он вообще не-
существен. Второй вопрос касается ссылки в стержневой ветви программы
на подпрограмму по имени $. . . , т. е. на подпрограмму, вызываемую из ма-
кроинструкций $IN.DECh $OUT.DEC. Предположительно эта ссылка также
появится в таблице внешних ссылок, модуль, содержащий эту точку входа,
будет загружен и перемещен, а ссылка на $. . . из стержневой ветви должна
быть внесена в объектный код, как только станет известен физический ад-
рес загрузки.
9.7. ЗАГРУЗЧИК-РЕДАКТОР-КОМПОНОВЩИК КАК СЛУЖЕБНАЯ ПРОГРАММА
ОПЕРАЦИОННОЙ СИСТЕМЫ
Хотя процедуры, рассмотренные в предыдущем разделе, могут показать-
ся довольно сложными, реальные трудности привносит необходимость уде-
лять внимание деталям — вычислению смещений и коэффициентов переме-
щения, — а не самим концепциям. Чтобы увидеть, что процесс решения этой
задачи в действительности довольно простой, будет полезно разбить всю ра-
боту на. три отдельных фазы.
Наипростейшей фазой процедуры является загрузка объектного кода в
оперативную память. Все, что должно здесь быть известно в дополнение к
тому, что объектный код должен быть загружен — это ФАЗ модуля. В нашем
примере мы решили взять такой ФАЗ для первого объектного модуля, что-
бы он загружался с ячейки 001000, а последующие модули загружались в
последующих доступных ячейках. Это практически все, что необходимо, да
и можно, сказать о загрузке.
Следующая фаза заключается в подстройке чувствительных к перемеще-
нию слов в различных объектных модулях. Поскольку при этом процессе
слова в объектном коде фактически подвергаются изменениям, эту фазу не-
редко называют редактированием объектных модулей. Для выполнения ре-
дактирования должны быть известны АЗА модуля, его ФАЗ и ячейки, содер-
жащие слова в модуле, чувствительные к перемещению. Поскольку эта ин-
формация доступна, редактирование просто сводится к прибавлению к каж-
дому такому слову фиксированного числа (ФАЗ — АЗА).
Теперь, когда различные модули загружены и отредактированы, остается
единственная фаза, называемая компоновкой1 объектных модулей. Сюда
входит определение адресов или смещений к ячейкам модуля, на которые
имеются ссылки из другого модуля. Для этой работы необходима информа-
ция о том, где в одном модуле делается внешняя ссылка и каково положе-
ние символа, на который производится ссылка, в другом модуле. Как мы
знаем, эта информация переносится соответственно в таблице внешних ссы-
лок и в таблице глобальных символов.
Если даны задачи, подлежащие решению, а также доступна необходимая
для этого информация и нужны процедуры для выполнения описанных выше
трех фаз, то представляется разумным для выполнения этой работы написать
утилиты операционной системы. Подобные подпрограммы поставляются с
большинством операционных систем, хотя нет ничего необычного и в том,
когда для всех трех этих работ используется одна программа, часто называе-
мая компоновщиком (или редактором связей). (В этом случае компонов-
щик делает больше, чем просто обеспечивает межмодульные связи.)
Какая информация необходима, чтобы такая программа могла, как и в
предыдущем разделе, построить исполнимый модуль? Нужно представить
только имена объектных файлов, вовлеченных в процесс компоновки. Как
только названия файлов станут известны, все файлы могут быть загружены
один за другим, начиная с соответствующего ФАЗ, как описано выше. Когда
все файлы загружены (вместе с соответствующими таблицами) , могут быть
отредактированы чувствительные к перемещению слова и установлены свя-
зи, необходимые для внешних ссылок. Таким образом, чтобы дать основной
пример этой главы, нам необходимо только информировать компоновщик
об именах объектных файлов, MAIN и SUBR. Компоновщик загрузит MAIN
(предположительно, начиная с ячейки 001000), загрузит SUBR сразу же вслед
за ним, затем отредактирует оба файла (хотя, как мы видели, для SUBR ре-
1 В переводной литературе этот термин иногда передается как связывание. — Прим,
перев.
у*
195
194
дактирование не требуется) и установит связи, необходимые для обращений
к внешней подпрограмме из MAIN.
Что если в этом примере имена файлов будут даны компоновщику не в
порядке MAIN, SUBR, а в порядке SUBR, MAIN? В таком случае подпрограм-
ма SUBR будет загружаться, начиная с ячейки 001000, a MAIN - непосред-
ственно вслед за ней. В одном из упражнений читателю предлагается пока-
зать, что, хотя при этом в оперативной памяти порождается другой оконча-
тельный набор объектного кода, существенного воздействия на исполнимую
программу это не оказывает.
Теперь нам известна входная информация компоновщика — имена объект-
ных файлов — и мы также знаем его выходную информацию — машинно-
исполнимая программа. Эту программу, т. е. набор объектного кода, мы бу-
дем называть абсолютным загрузочным модулем. Мы используем здесь сло-
во абсолютный в том смысле, что адреса, в которые теперь помещается
объектный код, являются абсолютными (физическими) адресами в противо-
положность адресам в объектных файлах, которые брались относительно
АЗА и которые в общем случае при загрузке и перемещении изменялись. Те-
перь компоновщик может поступить с абсолютным загрузочным модулем
по-разному. Он может просто вернуть управление супервизорной программе,
оставив загруженный модуль в оперативной памяти для последующего его
использования программистом. Компоновщик может даже начать выполне-
ние программы, поскольку ему известен стартовый адрес загруженного
модуля. Это так называемый режим загрузки с последующим выполнением
программы, который реализуется путем пересылки стартового адреса в PC.
Третье действие, зачастую выполняемое компоновщиком, состоит в со-
хранении загрузочного модуля в файле. Здесь требуется определенное внима-
ние, поскольку должен быть сохранен не только объектный код исполнимой
программы, но вместе с ним стартовый адрес. Отсюда возникает другой во-
прос. Если компоновщику для загрузки, редактирования и компоновки бы-
ло предоставлено несколько объектных файлов и если каждый из этих фай-
лов располагает в своей таблице стартовым адресом для модуля, то как ком-
поновщик может узнать, который из них является стартовым адресом для
загрузочного модуля? Ответ весьма простой. Если объектные файлы были
построены правильно, то один и только один из них будет содержать действи-
тельный стартовый адрес, а именно, тот модуль, который мы считаем основ-
ной программой. Если есть несколько модулей с действительным стартовым
адресом, или нет ни одного такого модуля, то компоновщик будет не в со-
стоянии определить стартовый адрес загрузочного модуля. Еще одним (и
последним) фрагментом информации, который требуется сохранять в файле
загрузочного модуля, является сам фактический адрес загрузки. Тогда про-
гон исполнимой программы сводится к загрузке объектного кода из файла
загрузочного модуля в оперативную память и запуску выполнения со старто-
вого адреса.
Последний способ, каким компоновщик может обрабатывать загрузоч-
ный модуль, состоит в сохранении его и его стартового адреса в файле, но в
перемещаемом формате. То есть после загрузки такого файла для выполне-
ния определяется (каким-то способом) ФАЗ и затем редактируются слова,
чувствительные к перемещению. Ясно, что если загрузочный модуль должен
196
сохраняться в этом формате, то в дополнение к объектному коду и чувстви-
тельному к перемещению стартовому адресу должна также сохраняться глав-
ная таблица перемещения для всего модуля. Хотя этот способ обращения с
загрузочным модулем может показаться не очень эффективным, из обсуж-
дения в разд. 9.9 станет ясно, почему он может оказаться желательным или
даже необходимым.
9.8. ВЫПОЛНЕНИЕ ЗАГРУЗОЧНОГО МОДУЛЯ
В предыдущем разделе мы отмечали, что если компоновщик после выпол-
нения своей задачи оставляет абсолютный загрузочный модуль в оперативной
памяти, то запуск выполнения модуля просто сводится к установке в PC его
стартового адреса. На практике, однако, необходимо еще присвоить значение
указателю стека (R6). Утилита, называемая процессором прогона, устанавли-
вает SP таким образом, чтобы размер доступного стекового пространства
оказался достаточным для большинства целей (см. разд. 8.4, где описаны две
возможные схемы). Действие процессора прогона объясняет наше предполо-
жение в гл. 8 о том, что в оперативной памяти устанавливается аппаратный
стек с соответственно инициализированным указателем.
Если абсолютный загрузочный модуль был сохранен в файле, то процессо-
ру прогона должно быть сообщено имя этого файла. После этого он может
загрузить модуль в оперативную память (используя сохраненный адрес за-
грузки) , установить указатель стека и начать выполнение загруженного моду-
ля с (сохраненного) стартового адреса. Наконец, если загрузочный модуль
был сохранен как перемещаемый загрузочный модуль, то либо процессору
прогона должен быть сообщен адрес для загрузки, либо он сам должен при-
нять решение о том, какой адрес для этого использовать, загрузить модуль,
начиная с этого адреса, и, прежде чем начать выполнение, отредактировать
чувствительные к перемещению слова. Такой процесс, при котором относи-
тельные адреса модуля делаются абсолютными, нередко называется абсо-
лютной загрузкой.
9.9. ПОЗИЦИОННО-НЕЗАВИСИМЫЙ КОД
Исследуя в гл. 7 различные режимы адресации с программным счетчиком
(см., в частности, с. 128), мы отмечали, что действия в режимах 3 и 6 были
идентичными — эффективной ячейкой являлась ячейка памяти, адрес кото-
рой задавался в инструкции. (В качестве примера мы использовали там ин-
струкции INC BUFFER и INC @#BUFFER, в обоих случаях увеличивалось
содержимое ячейки памяти с символьным адресом BUFFER.) В режиме 3
частью инструкции становится фактический адрес, на который производится
ссылка, тогда как в режиме 6 используется не сам адрес, а смещение от теку-
щего значения PC к этому адресу, Теперь видно, что при обсуждении переме-
щения такое различие становится существенным. Если фактический адрес
ячейки памяти используется как часть инструкции, то это слово оказывает-
ся чувствительным к перемещению программы. С другой стороны, если ис-
пользуется смещение к адресу от текущего значения PC, то оно к перемеще-
нию не чувствительно. Вообще инструкцию, не содержащую чувствительных
^97
к перемещению слов, называют позиционно-независимой, поскольку любое
ее перемещение в оперативной памяти не оказывает никакого влияния на
правильность выполнения. Подпрограмма из данной главы с точками входа
МАХ и MIN, объектный файл которой мы назвали SUBR, представляет имен-
но такой случай, поскольку ни одна из ее инструкций не чувствительна к пе-
ремещению — подпрограмма состоит исключительно из позиционно-независи-
мого кода (однако значения символов МАХ и MIN чувствительны к переме-
щению, хотя код, составляющий инструкции модуля, - нет). При написании
этой подпрограммы не предпринималось никаких сознательных усилий, что-
бы она была позиционно-независимой, она просто получилась такой. Но есть
ситуаций, когда может появиться стремление специально сделать код модуля
нечувствительным к перемещению. Хотя подробности этого вопроса лежат
за пределами основной темы главы, мы укажем некоторые обстоятельства,
при которых позиционно-независимый код может оказаться наиболее жела-
тельным.
Типичная операционная система состоит из большого числа утилит для вы-
полнения различных задач программиста. Ассемблер и компоновщик явля-
ются двумя из них, мы упоминали также стандартную программу для работы
с файлами. Упомянутый выше процессор прогона — еще один пример утили-
ты операционной системы. Поскольку такие программы обычно весьма ве-
лики и поскольку многие из них требуются не так уж часто, с целью эконо-
мии оперативной памяти (одного из наиболее ограниченных ресурсов) опе-
рационные системы зачастую не хранят эти стандартные программы постоян-
но резидентными в оперативной памяти. Вместо этого они сохраняют их (на-
пример, на диске) и заносят в оперативную память при поступлении запроса
от программиста на использование соответствующей утилиты. Эта ситуация
осложняется еще тем, что во многих операционных системах доступная (сво-
бодная) оперативная память не является статической-, объем свободной па-
мяти растет по мере удаления из оперативной памяти модулей (сохраняе-
мых, вероятно, на диске) и сокращается, например, когда в память заносят-
ся утилиты для выполнения некоторой задачи. Поэтому, когда возникает
необходимость занести в оперативную память какую-то программу, то вызы-
вается подпрограмма операционной системы (которая постоянно резидент-
на) , называемая диспетчером памяти, для определения того, может ли данная
утилита разместиться в памяти в настоящее время. Поскольку доступная па-
мять распределяется динамически, данная системная утилита не всегда бу-
дет загружаться в одни и те же ячейки при каждом занесении ее в память.
Если это так, то исполнимый модуль, составляющий такую программу, не
может быть абсолютным загрузочным модулем — он должен быть перемеща-
емым. Конечно, если такой модуль — естественно перемещаемый, т. е. состо-
ит исключительно из позиционно-независимого кода, то он может быть за-
несен в память и выполнен незамедлительно; никакого редактирования слов,
чувствительных к перемещению не требуется, поскольку таких слов просто
нет. При больших модулях, часто вызываемых операционной системой, та-
кое устранение редактирования может приводить к существенной экономии
времени.
Мы не утверждаем, что написание позиционно-независимого кода являет-
ся делом тривиальным. Во многих случаях это делать довольно трудно, если
198
вообще возможно, поскольку при этом йорождаются неестественные кон-
струкции программ. Чтобы понять, как это влияет на программиста, читатель
может взять одну из программ, написанных им ранее, и попытаться придать
ей новую, насколько это возможно, форму, построив для нее позиционно-не-
зависимый код. В среде операционной системы написание естественно пере-
мещаемых программ может давать существенный выигрыш. В обычных си-
туациях программирования, с которыми мы здесь в основном сталкиваем-
ся, в таком типе кодирования обычно не возникает необходимости, а если он
приводит к программам, которые трудно читать, понимать и сопровождать,
то он просто нежелателен.
9.10. НЕСКОЛЬКО ЗАКЛЮЧИТЕЛЬНЫХ ЗАМЕЧАНИЙ
Прежде чем закончить эту главу, мы хотим предупредить возможные не-
верные толкования, которые могли бы появиться у читателя в результате на-
шего обсуждения. Процесс загрузки-редактирования-компоновки мы описа-
ли как довольно простой как в концептуальном, так и в практическом пла-
не. В этом отношении мы не были некорректными. Но выпускаемые произ-
водителями ЭВМ компоновщики как утилиты операционных систем могут
предоставлять программисту такие возможности, которые намного расширя-
ют упомянутую здесь простую процедуру загрузки-редактирования-компо-
новки. При этом такие утилиты могут быть весьма и весьма сложными, что
может потребовать от программиста изучения довольно изощренного языка,
поскольку иначе он не сможет взаимодействовать с этой утилитой.
Основной пример этой главы имеет дело с привязкой подпрограммы к
основной программе. Из этого читатель может заключить, что сердцевиной
процесса компоновки всегда является какая-то основная программа, к кото-
рой должны подключаться подпрограммы, состоящие из исполнимого кода.
Действительно, очень часто на практике это так, но компоновщик ни к коей
мере не ориентируется на стержневую ветвь программы. Вспомним, что ас-
семблер при построении объектных файлов всегда генерирует в точности
один и тот же тип файла, независимо от вида исходного кода или его пред-
назначения. Затем компоновщик вместо того, чтобы привязывать некоторые
объектные файлы к центральному объектному файлу (стержневой ветви),
рассматривает свою задачу как связывание идентичных по структуре объект-
ных файлов. Нет причин, почему эти файлы должны быть подпрограммами
или даже почему они вообще должны содержать исполнимый код. Пока ас-
семблер в состоянии строить объектный файл, т. е. пока исходный код может
ассемблироваться, такие объектные файлы могут загружаться, редактиро-
ваться и компоноваться. Действительно, в нескольких упражнениях один из
объектных модулей состоит исключительно из данных — набора директив
.WORD.
9.11. УПРАЖНЕНИЯ
9.2.1. Если EXSUB - внешне определенный символ, то объясните различие между об-
работкой ассемблером инструкций JSR R2, EXSUB и JSR R2, @#EXSUB.
9.2.2. Если EXSUB - внешне определенный символ, то объясните, как ассемблер бу-
дет обрабатывать такую конструкцию:
MOV # EXSUB, R3
JSR R2,(R3) ' 199
9.2.3. Предположим, что EXBUFR — внешне определенный символ. Объясните, как
ассемблер будет генерировать код для следующих инструкций:
a) MOV #4, EXBUFR
б) CLR @#EXBUFR + 4
в) MOV EXBUFR - 2, R2
9.2.4. В разд. 8.8 мы утверждали, что если подпрограмма использует один или не-
сколько регистров общего назначения, то хорошая практика программирования состоит
в сохранении содержимого этих регистров до их использования и в последующем вос-
становлении его перед возвращением управления к основной (вызывающей) части про-
граммы. Объясните, почему сохранение и восстановление содержимого регистров, ис-
пользуемых внешней подпрограммой, является существенным моментом, а не просто
хорошей программистской практикой.
9.4.1. Рассмотрим следующую основную программу:
.GLOBL COUNT, BUFFER
START: MOV # BUFFER, R2
LOOP: INC (R2)+
DEC COUNT
BNE LOOP
HALT .END START
а) Постройте таблицу символов для этой программы.
б) Постройте таблицу внешних ссылок для объектного файла этого модуля.
9.4.2. Рассмотрим следующую ’’программу”:
DAYS :: .WORD 31., 28., 31., 30., 31., 30.
.WORD 31., 31., 30., 31., 30., 31.
.END
(Данные состоят из числа дней в месяцах невисокосного года.)
а) Сможет ли ассемблер ассемблировать эту ’’программу” в объектный файл? Дайте
пояснения.
б) Если ответ на п. а) утвердительный, то какими будут стартовый адрес, таблица
внешних ссылок и таблица глобальных символов?
9.5.1. Постройте таблицу перемещения для программы из упражнения 9.4.1.
9.5.2. Обращаясь к упражнению 9.4.2, напишите основную программу, которая будет
распечатывать число дней в месяце, когда месяц задается как число от 1 до 12 включи-
тельно. Ввод номера месяца и вывод числа дней должны производиться в десятичном ви-
де (соответственно с помощью макроинструкций $IN.DECh SOUT.DEC). (В данном
случае не нужно пытаться обрабатывать несуществующие месяцы, такие как —3 или 14.)
Задайте символ DAYS как внешний (с помощью директивы .GLOBL.)
а) Напишите и ассемблируйте основную программу.
б) Опишите стартовый адрес, таблицу внешних ссылок, таблицу глобальных симво-
лов, адрес загрузки ассемблера и таблицу перемещения, которые являются составными
частями объектного файла.
в) Расширьте этот пример программирования, написав основную программу, кото-
рая будет определять число дней в месяце исходя из месяца и года. (Очевидно, что по-
требуется единственная модификация при обработке февраля в високосном году. Та-
ким образом, ввод комбинации 2, 1985 будет порождать на выходе число 28, тогда как
комбинация 2, 1928 должна генерировать 29.)
9.6.1. (Продолжение упражнения 9.5.2.) Назовем объектный файл,построенный в п. а)
упражнения 9.5.2, именем MONTHS, а объектный файл из упражнения 9.4.2 NODAYS.
Пусть объектный код из NODAYS добавляется к MONTHS и если MONTHS загружается,
начиная с физической ячейки 004204, дайте ответы на следующие вопросы.
а) Каков физический адрес загрузки NODAYS?
б) Как разрешается ссылка (или ссылки) на DAYS в основной программе. Конкрет-
нее, каким будет конечное содержимое слов в стержневой ветви, которое не может
быть порождено ассемблером?
в) Как подстраиваются чувствительные к перемещению слова в стержневой ветви?
9.6.2. В подпрограмме на рис. 9.6.3 точку входа МАХ можно убрать, а ссылку делать
как на MIN —10. Тогда в стержневой ветви (см. рис. 9.6.1) строка 3 должна быть заме-
нена на .GLOBL MIN, а строка 13 на JSR Rl, MIN-10. Как повлияет это на ассемблиро-
вание стержневой ветви и подпрограммы и на процесс разрешения внешних ссылок при
добавлении объектного кода подпрограммы к стержневой ветви?
9.7.1. (Продолжение упражнения 9.6.1.)
а) Загрузите, отредактируйте и скомпонуйте файлы MONTHS и NODAYS из упражне-
ния 9.6.1. Выполните полученный в результате модуль и проверьте его работу.
б) Сделайте то же самое, что и в п. а) для основной программы из п. в) упражнения
9.5.2.
9.7.2. Тщательно и подробно объясните, как будет выглядеть загрузочный модуль,
если компоновщик загрузит объектный модуль SUBR (содержащий точки входа МАХ и
MIN), начиная с ячейки 001000, a MAIN непосредственно за ним. (Определения модулей
MAIN и SUBR см. в разд. 9.6.) В частности, выясните, куда загружается объектный
модуль, как подстраиваются чувствительные к перемещению слова в каждом модуле,
как заполняются ссылки из основной программы на МАХ и MIN и каким является стар-
товый адрес загрузочного модуля. Наконец, проверьте, что результирующий загрузоч-
ный модуль .является ’’той же самой” исполнимой программой в том смысле, что при за-
данной входной информации он будет порождать ту же самую выходную информацию,
что и загрузочный модуль, построенный в разд. 9.6.
9.7.3. Пусть MODI будет названием объектного файла для программы
.GLOBL DATA1
DATA2:: .WORD 101242
START: MOV @DATA1, R3
HALT
.END START
a MOD2 — именем объектного файла для
.GLOBL DATA2
DATA1:: .WORD DATA2
.END
Предположим, что MODI загружается, начиная с физической ячейки 001000, a MOD2 —
непосредственно за ним.
а) Каким будет ФАЗ для MOD2?
б) Каким будет стартовый адрес загрузочного модуля?
в) Какое потребуется (если потребуется) редактирование чувствительных к переме-
щению слов?
г) Укажите явным образом, каким будет объектный код для загрузочного модуля,
д) Какое число будет пересылаться в R3 при выполнении загрузочного модуля?
201
200
ГЛАВА 10. ЗНАКОВЫЕ КОДЫ
10.1. ОБРАБОТКА ЗНАКОВ
Программы и подпрограммы, которые мы рассматривали до сих пор, ка-
сались исключительно задачи обработки чисел — нахождения максимума или
минимума, сортировки в определенном порядке и т. п. Исторически сложи-
лось так, что первые ЭВМ явно предназначались для этого типа обработки чи-
сел, например для нахождения числовых решений дифференциальных уравне-
ний. Но, за возможным исключением очень специализированных ЭВМ или ма-
шин для научных исследований, все современные ЭВМ общего назначения (и
среди них PDP-11) существенное количество времени тратят на обработку
текстов — цепочек знаков. Действительно, в некоторых коммерческих орга-
низациях подавляющая часть процессорного времени отводится для такой
обработки знаков. Примером подобной деятельности, столь популярной в
настоящее время и известной как обработка текстов, является составление,
ведение и распечатка списков адресатов.
Если какой-либо знак, такой как Q, или даже цепочка знаков, например
MADE IN USA, должны обрабатываться (каким-то образом манипулировать-
ся ЦП), то этот знак или цепочка знаков должны располагаться в оператив-
ной памяти. Поскольку нам известно, что слова оперативной памяти могут
занимать только 16-битовые числа со знаком или без знака, то очевидно, что
мы должны преобразовать знаки в числа, называемые знаковыми кодами,
чтобы можно было хранить и обрабатывать тексты в оперативной памяти.
10.2. ПРЕОБРАЗОВАНИЕ ЗНАКОВ
Существует только одно руководящее правило, которого следует придер-
живаться при преобразовании знаков в числа — схема преобразования долж-
на разным знакам назначать разные числа. Таким образом, если мы, напри-
мер, решаем назначить число 143 знаку Q и знаку @, то при назначении чис-
ла обоим этим знакам проблемы не будет. Однако обратная операция — на-
значение знака числу 143 - окажется определенной плохо, поскольку нет
возможности узнать, заменяет ли число 143 знак Q или знак @. Пользуясь
математической терминологией, можно сказать, что функция, назначающая
числа знакам, должна быть обратимой, или взаимно-однозначной.
Если мы понимаем это единственное ограничение, касающееся однознач-
ности приписывания знаков числам, то нет никаких теоретических причин
для предпочтения одной схемы какой-либо другой. Однако существует ряд
практических соглашений, которые помогут нам в разработке схем назначе-
ния знаковых кодов. Например, какое бы число мы ни назначили коду для
знака 0 (не следует путать с числом 0), можно показать, что знаку 1 удобно
назначить такое число, которое на единицу больше, чем код для 0. Аналогич-
но знаку 2 мы назначаем число на единицу большее, чем код для 1, и т. д.
вплоть до знака 9.
Имея дело с нечисловыми знаками, мы можем решить, что код для В дол-
жен быть на единицу больше, чем код для А, код для С на единицу больше,
чем код для В, и т. д. Со строчными знаками, а, Ъ,... ,z, мы можем обойтись
аналогично. То, как надо обходиться со специальными знаками, $, #,&),:
202
и т. п., представляет определенную проблему, поскольку у них нет естествен-
ного или лексикографического порядка, которым можно было бы руковод-
ствоваться.
Решая, какой код присвоить данному знаку, мы должны действовать ра-
ционально, отчасти исходя из надежды, что принятые нами решения облег-
чат в будущем трудности программирования обработки текстов (этой темы
мы касаемся лишь вкратце, перенеся ее в основном в упражнения). Но здесь
играют роль три других соглашения. Первое из них касается ’’накладных рас-
ходов” оперативной памяти, вовлеченной в обработку знаков. В английском
языке общеупотребительны примерно 100 знаков, даже если мы включаем
сюда такие ’’непечатаемые” знаки, которые можно найти на терминалах ЭВМ —
типа перевода строки, возврата каретки, знака возврата на шаг и т. п. Поэто-
му, если мы начинаем схему преобразования с числа 0 (независимо от того,
какой знак будет приписан к числу 0), то обнаруживаем, что для однознач-
ного преобразования всех знаков можем использовать числа от 0 до 100 или
около того. Значение этого выбора заключается в том, что такое число может
удерживаться в отдельном байте оперативной памяти, а не в целом слове.
И чтобы читатель не считал все это неважным, рассмотрим следующий при-
мер. В списке адресатов типичная комбинация фамилии и адреса занимает
примерно от 50 до 60 знаков. Если бы такие знаки запоминались по одному
знаку на слово, то даже при небольшом списке адресатов из 100 фамилий и
адресов потребовалось бы 5000 слов оперативной памяти. Если же один
знак занимает один байт, то требуется только 2500 слов. Это, конечно, из-
рядная экономия объема памяти, и такая схема представляется несомнен-
ным улучшением предыдущей.
Второе соглашение, играющее роль при назначении кодов знакам, касает-
ся стандартизации. Мы можем назначить знаковые коды по своему выбору,
но если наши коды будут отличаться от кодов, назначенных в других органи-
зациях, использующих ЭВМ, то мы столкнемся с серьезными проблемами
при необходимости взаимодействия с такими организациями. По этой причи-
не наиболее полезной является какая-то общепринятая схема кодирования.
Хотя среди пользователей ЭВМ универсальное кодирование до сих пор не до-
стигнуто (и в некоторых случаях по обоснованным причинам), ситуация все-
таки не является хаотической: существует только несколько широко распро-
страненных схем кодирования, и по большей части преобразование между ни*
ми не представляется такой уж сложной задачей.
Третье соглашение при выборе процедуры кодирования знаков, на кото-
рое отчасти влияет и концепция стандартизации, касается реальной аппарату-
ры ЭВМ; оно будет обсуждаться в следующем разделе.
10.3. КОД ASCII
Прежде чем переходить к преобразованию, представляющему первосте-
пенный интерес для пользователей ЭВМ PDP-11, упомянем несколько обще-
употребительных схем кодирования. Одной из них является так называемый
код BCD (Binary-Coded-Decimal — двоично-кодированное десятичное пред-
ставление) . Этот код может оказаться полезным, когда нужно преобразовать
цепочку числовых знаков, например цепочку из трех знаков ”198” в число
198, которое затем будет подвергаться арифметическим манипуляциям. Рас-
203
ширением этого кода является EBCDIC (Extended-Binary-Coded-Decimal-Inter-
change-Code — расширенный двоично-кодированный десятичный код для об-
мена информацией), преобразующий как числовые, так и буквенные цепоч-
ки; он широко используется в ряде организаций,
В оставшейся части книги нас особо будет интересовать код ASCII - (Ame-
rican-Standart-Code-for-Information-Interchange — американский стандарт-
ный код для обмена информацией). На этом коде мы сосредоточимся по
двум причинам. Во-первых, в настоящее время этот код широко применяет-
ся на ЭВМ общего назначения и обладает рядом особенностей, значительно
облегчающих его использование с точки зрения программиста. Более веская
причина обусловливается аппаратурой, поскольку некоторые устройства,
обычные для многих систем ЭВМ PDP-11, фактически генерируют код ASCII.
Поэтому, когда мы, например, нажимаем на терминале клавишу S, то в ре-
зультате (как мы увидим в гл. 12), код ASCII для знака S передается в ЭВМ.
Аналогично, когда считыватель перфокарт прочитывает карту, пробитую в
колонке, представляющей знак $, он преобразует эту перфорацию в код
ASCII знака $, который передается в ЭМВ. Наконец, если надо распечатать на
АЦПУ такое сообщение, как OUT OF DATA, то код ASCII этих 11 знаков
должен быть послан на печатающее устройство, причем встроенные пробелы
также рассматриваются как знаки, фактически распечатываемые на АЦПУ.
Коды ASCII различных знаков даны в приложении В в восьмеричном пред-
ставлении. Хотя о специфическом кодировании, представленном в этой таб-
лице, мы ничего здесь больше говорить не будем, читателю полезно просмо-
треть таблицу, по возможности отметив какие-то особенности этой схемы
кодирования.
10.4. ДИРЕКТИВЫ.BYTE,.BLKB И .EVEN
Предположим, что мы хотим включить в программу код ASCII какого-
то текста, например MADE IN USA, для передачи его в конце концов на тер-
минал, АЦПУ или какое-либо другое устройство, работающее в коде ASCII.
Как можно это сделать? Мы знаем, что код ASCII предназначается для поме-
щения в индивидуальные байты и, следовательно, самая удобная форма та-
кого текста состоит из знаковых кодов в последовательных байтах. Таким
образом, этот текст появляется в оперативной памяти в таком виде, как по-
казано на рис. 10.4.1, где предполагается, что первый знак этого текста раз-
мещается в ячейке памяти 020466. Символ используется здесь для обозна-
чения знака пробела.
Конечно, это просто символьное представление памяти. То, что действи-
тельно находится в байтах, — это коды ASCII показанных знаков, так что ре-
альная ситуация представлена на рис. 10.4.2. Чтобы сформировать два байта,
составляющие коды знаков М и А, когда код знака М оказывается в млад-
шем байте, а код знака А — в старшем байте, необходимо слить эти байты
вместе в слово, которое нужно будет потом внести в программу с помощью
директивы .WORD. При слиянии байтов 101 и 115 образуется слово 040515,
которое, будучи помещенным в ячейку 020466 директивой .WORD 40515,
таким образом обеспечивает два первых знака сообщения. Обратите внима-
ние, что нам опять досаждает проблема, которая впервые появилась в гл. 4, —
трудность слияния в восьмеричном виде байтов для образования слов из-за
204
020467 1 1 020466 020467 1 1 020466
А 1 | м 101 ] 115
020471 Е 1 D 020470 020471 105 i 104 020470
020473 । ; м 020472 020473 111 | 040 020472
020475 1 1 N । 020474 020475 040 | 116 020474
020477 S 'i и 020476 020477 123 i 125 020476
020501 1 1 а 202500 020501 I 101 I 020500
Рис. 10 1 1 .4.1 Рис. 1С I 1 ).4.2
того, что байтовые границы не согласуются с границами восьмеричных цифр.
Аналогично мы можем сформировать слова, представляющие в своих стар-
ших и младших байтах коды ASCII следующих четырех пар знаков. Некото-
рые трудности возникают при обработке последнего знака (А в слове USA),
занимающего младший байт в ячейке 020500; вопрос заключается в том, что
делать со старшим байтом, который знакового кода не содержит. Поскольку
предполагается, что этот байт использоваться не будет, мы можем его запол-
нить нулями, так как с таким числом легче всего иметь дело. Итак, набор
слов, представляющих наш текст в ячейках от 020466 до 020500 включи-
тельно, будет таким:
020466 .WORD 040515
020470 .WORD 042504
020472 .WORD 044440
020474 .WORD 020116
020476 .WORD 051525
020500 .WORD 000101
Вся эта вычислительная нагрузка не вполне необходима, поскольку, если
ассемблер в состоянии генерировать заданное слово (обозначенное директи-
вой .WORD), он должен быть также способен сформировать заданный байт.
И действительно, именно это делает директива ассемблера .BYTE, сопровож-
даемая одним или несколькими байтовыми значениями, разделенными запя-
тыми. Чтобы сгенерировать код ASCII знаков нашего текста, мы могли бы
просто включить такие директивы:
020466 .BYTE 115,101,104,105,040,111
.BYTE 116,040,123,125,101
Несложный подсчет показывает, что последний байт, 101, действительно, по-
падает в ячейку 020500, и это приводит к небольшой проблеме. Генерируя
эти байты, ассемблер увеличивает свой счетчик распределения ячеек на 1 для
каждого сгенерированного байта, и теперь, когда текст закончился, этот счет-
чик имеет значение 020501. Это нечетное значение может создать проблему,
если вслед за ассемблированным текстом будет идти какая-то конструкция
пословного типа, которая должна ассемблироваться с четного (пословного)
адреса, например какая-либо инструкция или директива .WORD. Поскольку
сгенерировано нечетное число байтов, очевидна необходимость каких-то
205
средств для принудительного продвижения счетчика распределения ячеек ас-
семблера до четного значения, даже ценой потери байта. Это можно сделать с
помощью директивы ассемблера .BLKB п, которая аналогично директиве
.BLKW п резервирует п байт памяти. Следовательно, для гарантии того, что
счетчик распределения ячеек окажется на четном адресе, необходимо только
включить директиву .BLKB 1:
020466 .BYTE 115, 101,104, 105,040, 111
200474 .BYTE 116,040,123, 125, 101
02050J .BLKB 1
020502 (следующее ассемблируемое слово)
В представленной схеме есть два нежелательных аспекта. Во-первых, даже
если мы можем указать ассемблеру на необходимость генерации индивиду-
альных байтов, мы все же должны просматривать, какие коды ASCII для зна-
ков будут порождаться. И хотя вероятно, что для коротких сообщений, по-
добных тому, с каким мы имели дело до сих пор, это не очень обременитель-
но, для текстов произвольной длины такая работа может быстро надоесть.
Эту проблему мы решим в следующем разделе. Вторая трудность имеет сле-
дующую природу. Если мы намереваемся вставить в программу знаковые
коды на байтовой основе и если то, что будет ассемблироваться непосред-
ственно вслед за этим текстом, требует пословной границы, то мы должны
принять решение о том, включать или нет директиву .BLKB 1 после этого тек-
ста. Причем такое решение базируется на нашей способности правильно под-
считывать число байтов в тексте. Хотя такой подсчет не столь сложен, сколь
утомителен, все же это еще один потенциальный источник ошибок. Даже по-
лагая, что мы правильно подсчитали байты и вставили или не вставили .BLKB 1
для соответствующего выравнивания счетчика распределения ячеек ассембле-
ра, при последующих ассемблированиях программы мы можем захотеть вне-
сти какие-то изменения в текст: добавить знак здесь, удалить два знака там и
т. п. Такая манипуляция текстом может изменить счет байтов и, следователь-
но, при каждой модификации мы должны заботиться о пересчете байтов и о
правильном выравнивании адресной границы с помощью директивы .BLKB.
Эту проблему можно было бы легко решить, если бы мы могли просто за-
дать ассемблеру следующую команду: если текущее значение счетчика рас-
пределения ячеек нечетное, то увеличить его на 1 до следующего четного чис-
ла (адреса), оставив в программе потерянный байт; если же оно в настоящее
время четное, то оставить его нетронутым, т. е. игнорировать эту директиву.
Это мы делаем с помощью директивы .EVEN, которая, в противоположность
большинству других директив, не имеет аргументов. Существует еще дирек-
тива .ODD, гарантирующая нечетное значение счетчика распределения ячеек,
но она используется редко.
Теперь можно поместить байты сообщения MADE IN USA в программу
таким образом, что следующий адрес будет четным, с помощью директив
020466 .BYTE 115,101,104,105,040,111
020474 .BYTE 116,040,123,125,101
020501 .EVEN
020502 (следующее ассемблируемое слово)
206
где действие директивы .EVEN состоит во вставке директивы .BLKB 1. Если
же вместо этого текста мы используем MADE IN USA’, то никакого однобай-
тового блока вставляться не будет, поскольку последний байт текста, т. е.
код ASCII знака !, будет сгенерирован в ячейке 020501, при этом в счетчике
распределения ячеек ассемблера останется четный адрес 020502.
10.5. ДИРЕКТИВЫ .ASCII И .ASCIZ
Поскольку ассемблер уже в определенной степени занимается поиском в
таблицах (например, для нахождения мнемоник), то ему можно Передать
также задачу нахождения кодов ASCII для знаков при условии, что мы бу-
дем информировать его о том, какие знаки хотели бы генерировать. Это
мы делаем с помощью директивы ассемблера .ASCII, имеющей следующий
формат:
.ASCII #текст#
Здесь знак номера используется в качестве ограничителя, показывающего
ассемблеру, где строка текста начинается и где кончается. Сам ограничитель
# частью текста не является. Ограничителем может служить любой печатный
знак за исключением, конечно, знаков, которые появляются в самом тексте.
Таким образом,
.ASCII #MADEINUSA#
.ASCII /THE QUICK BROWN FOX/
.ASCII QMADE IN USAQ
это все приемлемые способы использования директивы .ASCII, хотя мы не
рекомендуем такого применения, как Q в третьем примере, поскольку это
лишь затрудняет читаемость текста. Мы предпочитаем такие ограничители,
как #, /, $; & и т. п., так как эти знаки не так часто встречаются в текстовых
строках. Обратите внимание, что директива
.ASCII SMADE IN USAS
будет ассемблироваться не так, как задумано. Ассемблер воспримет знак S
как ограничитель, начнет генерацию кода ASCII, но остановит ее, когда натк-
нется на S в слове USA. Таким образом, действие будет таким же, как от
директивы
ASCII SMADE IN US
(которая сгенерирует код строки MADE IN U), но ассемблер не будет знать,
как ему поступить со странными знаками AS в исходном коде.
Иногда бывает необходимо вставить в строку текста код ASCII непечатно-
го знака или знаков. Чаще всего в строку текста бывает необходимо вста-
вить комбинацию знаков возврата каретки и перевода строки. Поскольку
это управляющие знаки АЦПУ, то непосредственно в директиву .ASCII их
включить невозможно. Есть два способа включения их в текст. Во-первых,
мы можем построить три директивы наподобие следующих:
TEXT: .ASCII /ALAS,/
.BYTE 015,012
.ASCII /ALACK’/
207
020442 101 TEXT! .ASCII /ALAS»/<015><012>/ALACK!/
020443 114
020444 101
020445 123
020446 054
020447 015
020450 012
020451 101
020452 114
020453 101
020454 103
020455 113
020456 041 .EVEN
020460 ;<СЛЕДУМЕЕ СЛОВО ПРОГРАММЫ)
Рис. 10.5.1
(015 и 012 — это коды ASCII соответственно знаков возврата каретки и пе-
ревода строки.) Во-вторых, можно поместить эти два кода непосредственно
в директиву .ASCII, но за пределами ее ограничителей и заключить, их в угло-
вые скобки. Например:
TEXT: .ASCII /ALAS,/ <015> <012> /ALACK!/
На рис. 10.5.1 показан код, сгенерированный ассемблером для этой строки
текста. Обратите внимание на использование директивы .EVEN для гарантии
четного адреса для слова, следующего за текстом. Аналогично, если на тер-
минал, обладающий возможностью физического осуществления знака воз-
врата на шаг, выдать текст
.ASCII /ANGLE О/ < 010> /-=30/
то будет распечатано:
ANGLE 0 = 30
(010 — это код ASCII знака возврата на шаг, обеспечивающего черточку в
букве О).
Директива .ASCIZ идентична директиве .ASCII во всех отношениях, за
исключением того, что после генерации кода ASCII ассемблер вставляет один
дополнительный байт, содержащий число 000. ’’Знак”, кодом которого явля-
ется 000, называется NUL; он непечатный. Эта особенность реализуется в ас-
семблере для нужд программиста, который может также применять и дирек-
тиву .BYTE 0 после директивы .ASCII. В следующем разделе и в гл. 12 мы
увидим, как этим можно пользоваться.
10.6. СЧИТЫВАНИЕ И РАСПЕЧАТКА ТЕКСТА В КОДЕ ASCII
Возможность распечатки текста в коде ASCII может значительно улучшить
читаемость и понятность печатного вывода программ. Например, включив
короткие сообщения
THE NUMBER IN THEIR ORIGINAL ORDER:
и
THE NUMHER IN INCREASING ORDER;
обозначающие ’’числа в их первоначальном порядке” и ’’числа в порядке уве-
личения”, можно значительно улучшить доступность вывода программы или
подпрограммы, сортирующей числа (в упражнениях мы увидим некоторые
208
другие варианты использования такого распечатываемого текста). Подобно-
го рода сообщения могут распечатываться с помощью макроинструкции
$OUT. ASC. Таким образом, например, инструкции
$OUT.ASC #ТЕХТ, #13.
TEXT: .ASCII
/ALAS,/ <015> < 012> /ALACK!/
приведут к распечатке такого вывода:
ALAS,
ALACK!
Когда мы имеем дело с каким-то набором чисел (например, структурирован-
ным в виде вектора), то обычно точно знаем, сколько таких чисел рассматри-
ваем, поскольку, как правило, это. влияет на обработку, например на управ-
ление циклами. Однако при распечатке сообщений, подобных показанным
выше, нас обычно не интересует, сколько знаков содержится в сообщении,
так как эти знаки не требуют никакой обработки, кроме распечатки. Поэтому
неудобно, когда при использовании макроинструкции $OUT.ASC приходит-
ся подсчитывать число знаков, подлежащих выводу, чтобы можно было ука-
зать второй аргумент этой макроинструкции. Если мы подсчитаем неправиль-
но или если будут сделаны какие-то модификации текста, то может оказать-
ся, что будет распечатано урезанное сообщение или сообщение, содержащее
байты, которые в текст не предназначались. Существует вариант макроин-
струкции $OUT. ASC, при котором счетчика знаков указывать не надо. Един-
ственной передаваемой информацией, является адрес первого байта текста, и
макроинструкция $OUT.ASC будет продолжать распечатывать знаки кода
ASCII до тех пор, пока не встретит знак NUL. Тогда она вернет управление
программе пользователя. Таким образом, в последнем примере мы могли бы
воспользоваться преимуществом этой особенности, записав
SOUT.ASC #ТЕХТ
TEXT: \SCIZ /ALAS,/< 015X012)/ALACK!/
Аналогично, если мы выдаем макроинструкцию
$IN.ASC #BUFFER, #17
то подпрограмма будет принимать знаки, байт за байтом, помещая их после-
довательно в ячейках BUFFER, BUFFER+1, BUFFER+2 и т. д. до тех пор, по-
ка не примет пятнадцатый знак, который поместит в BUFFER+16; в этот мо-
мент подпрограмма вернет управление программе пользователя. Опять при
этом требуется, чтобы программист заранее знал, сколько знаков будет счи-
тываться, но во многих случаях это или не имеет значения, или невозможно.
Поэтому SIN.ASC имеет режим, аналогичный режиму макроинструкции
209
$OUT. ASC, при котором никакой счетчик не передается. Таким образом, на-
пример, макроинструкция
$IN.ASC #BUFFER
будет считывать знаки и помещать их в ячейки BUFFER, BUFFER+1 и т. д.
до тех пор, пока не встретит знак конца строки (возврат каретки). Его под-
программа в следующий байт оперативной памяти не вставит, а поместит ту-
да код NUL (ООО). Поэтому последовательные байты оперативной памяти,
сгенерированные при использовании этой версии макроинструкции SIN.ASC,
будут выглядеть точно так же, как если бы они были построены директивой
.ASCIZ. Используя этот режим, программист должен гарантировать, что блок
байтов, отведенный под эти знаки, имеет достаточный размер, чтобы туда по-
местился самый длинный ввод, включая и знак NUL; это необходимо, чтобы
избежать перекрытия другого кода в программе.
10.7. КОНСТРУКЦИЯ С АПОСТРОФОМ
Есть еще один способ заставить ассемблер генерировать код ASCII, на этот
раз в виде отдельных знаков. Если какой-то знак предваряется апострофом,
то ассемблер преобразует эту конструкцию исходного кода в однобайтовый
код ASCII для указанного знака. Таким образом, действие инструкции
MOVB#'W, R4
состоит в пересылке кода знака W, а именно, 127 — в R4. Эту инструкцию
можно (но менее удобно) записать как
MOVB #127,R4
Аналогично, запись
.BYTE 'P,'D,'P, '-,'1/1
эквивалентна записи
.ASCII #PDP-11#
но первую написать несколько труднее. В оставшейся части книги мы увидим
целый ряд применений этой конструкции.
10.8. УПРАЖНЕНИЯ
10.3.1. Определите пять слов, последовательные байты которых содержат код ASCII
10 знаков текста TIME FLIES.
10.3.2. Напишите подпрограмму, которая находит место первого появления заданно-
го знака в строке знаков. В частности, предположите, что R0 содержит адрес первого
байта в строке, где ведется поиск, R1 содержит код ASCII искомого знака (в своем
младшем байте), и пусть подпрограмма после возврата в вызывающую программу ос-
тавляет адрес заданного знака в R0. Таким образом, например, если первый байт строки
A0LOAF0OF0BREAD помещается в ячейке 002401 и если R1 содержит код знака F,
то после возврата R0 будет содержать 002406 (здесь 0 представляет знак пробела).
Единственной возможной ошибкой является отсутствие искомого знака в заданной стро-
ке. В настоящий момент мы не будем пытаться решить эту потенциальную проблему.
10.3.3. Каково соотношение между кодами ASCII прописных и строчных алфавитных
знаков?
10.3.4. Отыщите таблицу для кода EBCDIC и найдите соотношение (если оно имеет-
ся) между кодами прописных и строчных алфавитных знаков.
10.3.5. В разд. 10.3 мы утверждали, что считыватель перфокарт с помощью своей
внутренней аппаратуры может преобразовывать перфорации в колонках карты в код
ASCII соответствующего знака перед передачей информации о колонке карты в ЭВМ.
Однако некоторые считыватели перфокарт такое преобразование не делают; они переда-
ют в ЭВМ отражение перфораций в 12 строках каждой колонки, представляемое 16-би-
товым словом, каждый единичный бит которого обозначает перфорацию, а нулевой — ее
отсутствие (из 16 бит используются только 12). Например, если колонка карты содер-
жит перфорации в верхней, третьей сверху, и нижней (двенадцатой) строках, то переда-
ваемое в ЭВМ 16-битовое слово будет таким:
1610000000010000
или 120020 (в восьмеричном представлении). Найдите таблицу карточных кодов (назы-
ваемую иногда кодом Холлерита) и попытайтесь определить, насколько легко или труд-
но написать программу для преобразования этого кода в ASCII.
105.1. Напишите подпрограмму, которая удалит все пробелы (0) из строки текста в
коде ASCII, которая, как предполагается, оканчивается на NUL.
10.5.2. Напишите подпрограмму, которая преобразует все алфавитные строчные зна-
ки строки (которая оканчивается на NUL) в прописные.
10.5.3. Если R2 содержит в своем младшем байте код ASCII одного из знаков 0,1,...
. . . , 7, то какая инструкция преобразует содержимое R2 в число, представленное этим
знаком?
10.5.4. Предположим, что R1 содержит адрес первого байта строки, оканчивающейся
на NULh состоящей из одного, двух или трех числовых знаков, каждый из которых по-
падает в диапазон от 0 до 7. Напишите, подпрограмму, которая преобразует эту строку,
рассматриваемую как восьмеричное число, в двоичное число и оставит результат в R0.
(См. упражнение 10.5.3. Здесь может быть использована инструкция ASH.)
10.5.5. Рассмотрим строку, содержащую от одного до четырех знаков, каждый из ко-
торых является одним из числовых знаков 0,1,. . ., 9. Предположим, что строка окан-
чивается на NUL. Преобразуйте эту строку, рассматриваемую как десятичное число, в
двоичное число.
105.6. Напишите подпрограмму, которая при заданных адресах двух строк (каждая
из которых заканчивается NUL), определит, какая строка расположена первой в лекси-
кографическом порядке. (Эта информация может быть возвращена из подпрограммы
целым рядом способов.)
105.7. Напишите программу для размещения набора строк, каждая из которых пред-
полагается оканчивающейся на NUL, в лексикографическом порядке. (Это нетривиаль-
ное упражнение. Указание. Чтобы избежать перемещения строк в оперативной памяти
для размещения их по порядку, постройте вектор, элементами которого являются ад-
реса первых байтов строк. Таким образом, если
ADDR1: .ASCIZ /строка^
ADDR2: .ASCIZ /строка2/
ADDRN: .ASCIZ /строка^/
то вектор будет и меть вид
POINTR: .WORD ADDR1,. . . , ADDRN
Если мы должны поменять местами строку^ и строк Уд , то обмениваем вместо этого
ADDRI и ADDRJ в векторе.)
10.5.8. Преобразуйте 16-битовое двоичное число в строку из шести восьмеричных
цифр, которая является восьмеричным представлением этого числа.
210
211
10.5.9. В коде BCD одна десятичная цифра (0, 1,. . . , 9) кодируется четырьмя би-
тами, такими как 0000, 0001, . . . , 1001. Таким образом, одно 16-битовое слово ЭВМ.
PDP-11 может удерживать четыре таких представления BCD. (Эти четыре бита иногда на-
зывают полубайтами, хотя в литературе используется также и термин часть байта.) Та-
ким образом, десятичное число 1987 было бы закодировано в слове следующим образом:
0001 1001 1000 0111
"Т"
Использование кода BCD обусловлено тем, что относительно легко осуществлять пре-
образования строки десятичных числовых знаков кода ASCII в их внутренний формат
кода BCD и обратно (по легкости это преобразование лишь незначительно отличается от
обработки восьмеричных представлений).
а) Преобразуйте во внутренний формат кода BCD четырехзнаковые цепочки кода
ASCII. (Проверяя эту подпрограмму, вероятно, было бы полезнее всего распечатывать
результирующие числа в двоичном виде, используя макроинструкцию SOUT.BIN.)
б) Преобразуйте 16-битовое BCD-кодированное число в цепочку из четырех десятич-
ных знаков кода ASCII.
в) Несмотря на легкость взаимного преобразования кодов ASCII и BCD за попытку
выполнения арифметических действий над BCD-кодированными целыми числами прихо-
дится платить большую цену. Рассмотрим следующий простой пример:
0001 0010 0101 ’ ООН 1253
+1000 0001 0001 0101 +8115
1001 ООН ОНО 1000 9368
Как мы видим, двоичное сложение дает в результате правильное BCD-представление. Но
рассмотрим теперь
. 0001 0010 0101 1001 1259
+1000 0010 0111 1000 +8278
1001 0100 1101 0001 9537
Ясно, что здесь многое неверно, и происходит это по двум причинам. Во-первых, был пе-
ренос из бита 3 в бит 4, который не обрабатывался. И, во-вторых, должен был быть пере-
нос из бита 7 в бит 8, но его не было. В качестве нетривиального упражнения реализуйте
BCD-сложение 16-битовых слов, полагая для удобства, что BCD-кодированные числа рас-
полагаются в регистрах общего назначения.
(Вообще говоря, зга задача не из легких. Основывающиеся на такой BCD-арифмети-
ке ЭВМ — обычно микропроцессоры — в своем слове состояния процессора часто имеют
индикаторы полубайтового переноса, так что, к примеру, переносы из бита 3 в бит 4
могут быть обнаружены. Некоторые такие машины даже имеют специальные аппаратные
инструкции, называемые инструкциями десятичной подстройки, которые осуществляют
соответствующие подстройки BCD-кодированных слов после сложения или вычитания.)
10.6.1. Какой будет распечатка при выполнении программы, представленной на рис.
10.8.1? Этот пример показывает возможность использования ЭВМ для декодирования
замаскированных сообщений. Можно также закодировать какой-либо английский текст,
и, фактически, директивы .WORD в листинге образуют такую кодирующую программу.
Анализ использованной здесь схемы показывает, что она настолько проста, что едва ли
вызовет затруднения даже у новичка в криптографии. У читателя может появиться
желание изучить более изощренные и сложные схемы кодирования и соответствующие
методы декодирования.
10.6.2. Рассмотрим строку в следующем формате:
фамилия, имя, инициал
000000 110256 BLOCK: .WORD 110256»124202»110100»124202
000002 124202 -
ооооо4 110100
000006 124202
000010 040220 .WORD 040220»117216»040210,122256
000012 117216
000014 040210
000016 122256
000020 125236 .WORD 125236»110216,077250
00002? 110216
000024 077250
S
000026 012700 START: NOV «BOCK,R0
000000’
000032 000241 LOOP: CLC
000034 006020 ROR <R0) +
000036 020027 CNP R0,«START
000026’
000042 001373 BNE LOOP
000044 «OUT. ASC «BLOCK,«START-BLOCK
000100 «EXIT
000026’ .END START
Рис. 10.8.1
такую как JONES,JOHN,J и предположим, что каждая из этих трех цепочек оканчивает-
ся байтом NUL (000).
а) Из этой строки постройте другую строку в формате
имя]Ьиници ал. ^фамилия
чтобы получилось JOHNtyJ.tyJONES, где, как обычно,обозначает знак пробела. Здесь
будет полезна подпрограмма из упражнения 10.3.2. Мы полагаем, что каждая такая стро-
ка содержит точно две запятых и инициал из одного знака в середине. Предположим,
что ни одно ФИО не будет содержать более 401О знаков. Введите строку, используя мак-
роинструкцию $IN.ASC6e3 счетчика знаков, и выведите полученную в результате мо-
дифицированную строку, используя макроинструкцию $OUT.ASC, опять без счетчика
знаков.
б) Модифицируйте программу из п. а), чтобы она могла работать с именами, не со-
держащими инициала в середине. Таким образом, например, JONES,JOHN должно по-
рождать на выходе JOHN^JONES.
10.6.3. Предположим, что строка вводится с помощью макроинструкции $IN. ASC,
когда она используется в формате без счетчика знаков, так что результирующая строка
оканчивается на NUL. Напишите подпрограмму для нахождения числа знаков в такой
строке.
10.6.4. Напишите программу, которая вводит строку (длиной не больше, чем 20,0
знаков) и затем подставляет ее вместо знака ? в строке
THEty?JJBROWN|SFOX
(Это обычная задача, выполняемая редакторами текста и текстовыми процессорами.)
10.6.5. Рассмотрим строку знаков:
BY^THE^DAWN’S^EARLYULIGHT
Напишите программу, которая локализует первое появление заданного знака в этой
строке и затем стирает указанное число знаков из строки, начиная с данного знака. Та-
ким образом, если, например, программа получает на входе Н, 14, то должна породить
на выходе строку
BY^TRLY^LIGHT
Каким будет вывод для команды D.0? А для А,72?
213
212
10.6.6. (Продолжение упражнения 9.5.2.) Напишите программу, которая распечаты-
вает число дней в указанном месяце, когда месяц вводится в виде первых трех знаков
названия, таких как JAN, FEB и т. п.
10.6.7. (Продолжение упражнения 10.6.6.). Напишите программу, которая распечаты-
вает число дней в указанном месяце, когда ей задаются месяц (введенный в виде пер-
вых трех знаков) и год. (Смотрите упражнение 10.3.1 и 10.5.5.) Таким образом, строка
FEB, 1928, введенная с помощью макроинструкции $IN. ASC, должна порождать на вы-
ходе 29.
10.6.8. (Продолжение упражнения 10.6.7.) Проведите такое расширение упражнения
10.6.7, чтобы для ввода, например, APR, 1942, на выходе порождалась строка, говоря-
щая, что апрель 1942 г. содержит 30 дней:
APRILS 1942, CONTAIN Stf 3 O^DAYS
ГЛАВА И . ИНСТРУКЦИЯ TRAP
11.1. ЕЩЕ РАЗ О ПОДПРОГРАММАХ
В гл.8 мы в некоторых деталях познакомились с концепцией подпрограм-
мы, а в этой и последующих главах увидим, что подпрограммы могут быть в
высшей степени полезными, когда они пишутся или как внутренняя часть
основного программного модуля, или как внешняя часть, позднее привязан-
ная к стержневой ветви программы. Прежде чем переходить к основной теме
этой главы, дадим краткий обзор некоторых из этих идей.
Действия, лежащие в основе концепции подпрограммы, состоят в сохра-
нении и восстановлении содержимого PC — прежде чем отдать управление
подпрограмме, стержневая ветвь где-то сохраняет содержимое PC, так чтобы
подпрограмма после своего завершения могла восстановить его значение,
для возврата управления назад к стержневой ветви. То, где сохраняется со-
держимое PC, при общих рассуждениях нас не интересует. Оно могло бы, на-
пример, всегда сохраняться в какой-то фиксированной ячейке памяти, хотя
последствия этого породили бы серьезные ограничения для программиста.
В упражнении 8.6.5 мы предлагали разобрать схему, которая действительно
используется на некоторых машинах и по которой содержимое PC всегда со-
храняется в первом слове самой подпрограммы. На ЭВМ PDP-11, как мы зна-
ем, содержимое PC сохраняется в регистре общего назначения или, возмож-
но, в стеке, тогда как на других машинах, имеющих стек, но, вероятно, не
обладающих регистрами общего назначения, содержимое PC перед передачей
управления подпрограмме просто проталкивается в стек. Аппаратный стек
ЭВМ PDP-11 играет важную роль при использовании подпрограмм (вложен-
ные обращения к подпрограммам, подпрограммы, вызывающие друг друга,
и рекурсивные обращения к подпрограммам), и в целом ряде случаев мы
пользуемся этими преимуществами. Однако непосредственно в концепцию
подпрограммы стек не входит.
Другой темой гл. 8, представляющей определенный интерес, была передача
аргументов подпрограмме, и мы рассмотрели ряд способов, которыми это
может быть сделано. Если аргументы передаются подпрограмме через регист-
ры общего назначения или в стеке, то не видно явных преимуществ использо-
вания одного из регистров общего назначения в качестве регистра для пере-
214
хода к подпрограмме — мы можем просто протолкнуть в стек содержимое
PC, что и получается в результате применения конструкции JSR PC, SUBR.
Если же, с другой стороны, аргументы помещаются непосредственно вслед
за инструкцией JSR в директивах .WORD (полезный и популярный метод пе-
редачи аргументов) , то использование регистра общего назначения в качестве
регистра для перехода значительно облегчает ’’вытаскивание” этих аргумен-
тов подпрограммой, и в этом случае использование PC в качестве регистра
для перехода несколько неудобно, хотя получение аргументов, конечно, все-
таки возможно. Поэтому на машинах, инструкция JSR которых всегда про-
талкивает содержимое PC в стек (т. е. когда JSR пребывает только в един-
ственном формате), передача аргументов подпрограмме обычно выполняет-
ся с помощью других средств, нежели помещение аргументов в директивах
.WORD вслед за обращением к подпрограмме.
11.2. ГИПОТЕТИЧЕСКАЯ ИНСТРУКЦИЯ JTZ
Рассмотрим некую гипотетическую ЭВМ (и, конечно, не PDP-11), на кото-
рой реализована инструкция JTZ (Jump-Through-Zero — переход-через-ноль).
Ее действие описывается так:
HSP)^c(PC)
PC <- с (000000)
Мы видим, что когда JTZ выполняется, текущее содержимое PC проталкива-
ется в стек и PC получает новое значение из ячейки 000000. То есть выполне-
ние возобновляется с той ячейки памяти, адрес которой находится в ячейке
000000. Важно понимать, что переход, который здесь происходит, выполняет-
ся не к 000000, но через 000000.
Конечно, это своеобразная инструкция по сравнению с теми, с которыми
мы уже знакомы. Однако из-за сохранения (в стеке) с (PC) кажется, что она
может оказаться полезной для конструкции типа ”перейти-к-подпрограмме”,
поэтому мы вкратце рассмотрим эту идею. Временно предположим, что ин-
струкция JTZ реализована на ЭВМ PDP-11, и с целью получения листингов
программ мы даже назначим ей эффективный операционный код — 007200.
В иллюстративных целях используем подпрограмму, которая заменяет содер-
жимое R0 его абсолютным значением (рис. 11.2.1). Все,что необходимо здесь
делать, — это проверять c(R0). Если это число неотрицательное, то мы просто
возвращаемся; в противном случае мы заменяем c(R0) его отрицательным
значением и возвращаемся. Перед выполнением инструкции JTZ требуется
несколько предварительных действий.
Поскольку инструкция JTZ поместит в PC содержимое ячейки памяти
000000, мы должны в первую очередь гарантировать, что в этой ячейке нахо-
дится адрес подпрограммы. Это достигается в строке 43,где ABS — это сим-
вольный адрес точки входа в подпрограмму. И, конечно, перед выполнением
инструкции JTZ в R0 должно находиться число, абсолютное значение которо-
го необходимо найти (строка 55). Когда инструкция JTZ извлекается ЦП,
с (PC) увеличивается до 000616. Затем это число (адрес) проталкивается в
стек и PC присваивается значение содержимого ячейки памяти 000000, а
именно 001266 = ABS. Затем выполняется подпрограмма, и при завершении
ее выполнения для достижения правильного возврата необходимо только вы-
215
1 2 000000 000000 ZERO = 0 ;АДРЕС ДЛЯ ’ПЕРЕХОДА ЧЕРЕЗ’
172335 DATA: .WORD 172335
43 000556 012767 START: MOV «ABS»ZERO ;ЗАДАТЬ АДРЕС ПОДПРОГРАММЫ
001266’
000000
•
55 000610 016700 MOV DATA»R0 ;ЗАНЕСТИ В R0 ЧИСЛО
177164
56 000614 007200 JTZ ; И «ЕРЕИТИ-ЧЕРЕЗ-0
57 000616 ; (СЛЕДУИАЯ ИНСТРУКЦИЯ)
89 001266 005700 ABS: TST R0 :ПРОВЕРИТЬ ЗНАК
90 001270 100001 BPL RETURN ;НЕОТРИЦАТЕЛЬНЫЙ -- ХОРОШО
91 001272 005400 NEG R0 ; ИНАЧЕ ИЗМЕНИТЬ ЗНАК
92 001274 012607 RETURN: MOV (SP)+»PC .ВОССТАНОВИТЬ PC ИЗ ОСНОВНОЙ ПРОГРАММЫ
.
ТАБЛИЦА СИМВОЛОВ
APS 001266R RETURN 001274R ZERO = 000000
DATA 0O0000R START 000556R
Рис. 11.2.1
толкнуть верхний элемент стека (который содержит 000616) в PC. Таким об-
разом нам не нужна специальная инструкция наподобие RTS для возврата
после использования JTZ, поскольку возврат выполняется совсем просто.
Ситуация настолько проста, что о ней больше нечего сказать. Однако следует
обратить внимание на строку 1: ZERO = 0. Здесь мы создали символ ZERO,
но вместо того, чтобы использовать его где-либо в программе в качестве мет-
ки (такой как DATA или START) и, таким образом, дать возможность ассемб-
леру присвоить ему значение, мы непосредственно присваиваем значение это-
му символу (0) с помощью конструкции, состоящей из знака ’’равно”, так
называемого оператора прямого присваивания. Обратите внимание, что ZERO
появляется в таблице символов со значением 000000, хотя формат этого
табличного входа несколько отличается от обычного — знак ’’равно” показы-
вает, что значение этого символа было непосредственно присвоено, а не вы-
числено. Символ ZERO был использован в строке 43, в инструкции MOV
#ABS, ZERO, которая устанавливает содержимое ячейки 000000 при подго-
товке к выполнению JTZ. Здесь можно было точно так же выполнить MOV
#ABS, @#0. Мы мотивируем применение нашей конструкции тем, что хотим
подчеркнуть, что ZERO — это адрес, по которому мы пересылаем число (ад-
рес) ABS, поскольку такой прием в определенной степени увеличивает по-
нятность листинга программы. (В следующей главе, например, мы будем де-
лать такие прямые присваивания, как LF = 12, где 12 — это код ASCII знака
перевода строки. После такого присваивания ссылки в листинге программы
на символ LF будут существенно более значимыми, чем ссылки просто на
число 12.)
В программном сегменте на рис. 11.2.1 аргумент подпрограмме ABS мы
передавали через регистр R0. Если бы мы вместо этого решили передать такой
аргумент, поместив вслед за инструкцией JTZ директиву .WORD, содержа-
щую адрес числа, абсолютное значение которого должно быть найдено, то об-
216
1 000000 ZERO = 0 ;АДРЕС ДЛЯ ’ПЕРЕХОДА ЧЕРЕЗ’
9 000000 172335 ВАТА: .WORD 172335
35 000556 012767 START: MOV 0ABS.ZERO ;УСТАНОВИТЬ АДРЕС ПОДПРОГРАММЫ
001226’ 000000
57 000614 007200 JTZ ЯЕРЕЯТИ- МЕРЕ 3-0
58 000616 000000’ .WORD DATA 1АДРЕС ЧИСЛА
59 {(СЛЕДУМАЯ ИНСТРУКЦИЯ)
91 001226 017646 00000 ABS: MOV O(SP).-(SP) {ПОМЕСТИТЬ АДРЕС В СТЕК
92 001232 005776 000000 TST e<sp) {ПРОВЕРИТЬ ЗНАК
93 001236 100002 BPL RETURN .НЕОТРИЦАТЕЛЬНЫЙ — ХОРОВО
94 001240 005476 000000 NEG e<sp> { ИНАЧЕ ИЗМЕНИТЬ ЗНАК
95 001244 005726 RETURN: TST (SP) + {ПРОДВИНУТЬ SP ВВЕРХ НА 2
96 001246 062716 000002 ADD *2.(SP) •ПОДСТРОИТЬ АДРЕС ВОЗВРАТА
97 001252 012607 MOV (SPl+.PC { И ВОССТАНОВИТЬ PC ИЗ { ОСНОВНОЙ ПРОГРАММЫ
Рис. 11.2.2
работка в подпрограмме ABS была бы намного более сложной, как это пока-
зано в листинге на рис. 11.2.2.
До сих пор читателю не приходилось испытывать особых трудностей при
прослеживании деталей программирования, хотя и требовалось определенное
внимание в отношениии того, с какими числами производятся манипуляции,
поскольку несколько инструкций имеют двоякий характер. В частности, ин-
струкция в строке 91 помещает число (адрес) DATA на вершину стека; в
строке 95 указатель стека подстраивается для удаления числа DATA с верши-
ны стека, которое сослужило свою службу; в строке 96 возвратное значение
PC — на вершине стека — подстраивается для обхода директивы .WORD DA-
TA в стержневой ветви программы. Здесь требуется несколько более слож-
ная обработка в подпрограмме, чем в предыдущем примере, но читатель дол-
жен знать, что это не является специфическим недостатком инструкции JTZ —
такие же проблемы возникали бы, если бы вместо этой инструкции исполь-
зовалась JSR PC, АВS. Трудности появляются из-за того, что адрес возврата
находится в стеке, а не в каком-нибудь регистре.
Проблемы, с которыми приходится сталкиваться при использовании ме-
тода передачи аргументов подпрограмме с помощью инструкции JTZ, туск-
неют перед намного более серьезной трудностью. Если JTZ использует ячей-
ку 000000 для удержания адреса подпрограммы (чтобы можно было перейти
через нее), то как можно в программе осуществить обращение больше чем к
одной подпрограмме? В ячейке 000000 может находиться только один адрес
подпрограммы или, в лучшем случае, один адрес в один и тот же момент вре-
мени. Следовательно, если с помощью инструкции JTZ обращение осуще-
ствляется к двум или более подпрограммам, то единственный способ, каким
можно это выполнить, состоит в изменении содержимого ячейки 000000 на
соответствующий адрес точки входа в подпрограмму каждый раз при выпол-
нении JTZ. Поскольку инструкция типа JSR все-таки существует на ЭВМ
PDP-11 (а также и почти на всех других машинах), то представляется, что шь
217
струкция JTZ, если она вообще реализована, почти никогда не будет исполь-
зоваться.
11.3. ИНСТРУКЦИЯ TRAP
Читатель может выразить справедливое удивление, почему мы посвятили
целый раздел этой главы инструкции, являющейся гипотетической, не реали-
зованной в аппаратуре ЭВМ PDP-11 и вообще представляющейся мало полез-
ной с точки зрения программиста. Причина состоит в том, что PDP-11, хотя
действительно не реализует такую инструкцию, как JTZ, все-таки имеет не-
которую инструкцию, которая почти идентична по своим действиям JTZ, —
инструкцию TRAP. Действие инструкции TRAP состоит в проталкивании в
стек с (PC) и затем загрузке в PC содержимого некоторой фиксированной
ячейки памяти. Следовательно, TRAP делает в точности то же самое, что и
гипотетическая инструкция JTZ, хотя фиксированной ячейкой в этом случае
является 000034, а не 000000; однако TRAP фактически делает несколько
больше — она, кроме того, проталкивает в стек c(PSW) и получает новое со-
держимое PSW, опять из некоторой фиксированной ячейки памяти. Действие
инструкции TRAP описывается так:
4(SP) ^c(PSW)
4(SP) <-с(РС)
PC с (000034)
PSW с (000036)
Обратите внимание, что первым в стек проталкивается содержимое PSW, а
за ниту! — содержимое PC. Поэтому после выполнения инструкции TRAP на
вершине стека оказывается возвратное значение PC, а ’’старое” содержимое
PSW — на одно слово в стеке ниже. Как мы вскоре увидим, такой порядок
помещения в стек содержимого регистров приводит к определенным послед-
ствиям для программирования при использовании инструкции TRAP. Ячейки
памяти 000034 — 000036 называют TRAP-вектором.
Вспоминаем, что для возврата после перехода JTZ-типа требовалось толь-
ко вытолкнуть верхний элемент стека назад в PC. Однако в случае инструк-
ции TRAP ситуация несколько сложнее, поскольку она проталкивает в стек
два элемента. Поэтому здесь необходима специальная инструкция ЭВМ PDP-11
для восстановления содержимого PC и PSW. Это инструкция RTT (ReTum-
from-Trap — возврат из ловушки1), которая может быть описана следующим
образом:
PC <- (SP)t
PSW^ (SP)t
На некоторых моделях PDP-11 инструкция RTT не реализована. На этих мо-
делях с одинаковым успехом можно пользоваться инструкцией RTI, посколь-
ку за одним исключением их поведение идентично (смотрите описание этих
инструкций в приложении А). (Различие между RTT и RTI заключается в ре-
акции на состояние одного бита в PSW — бита трассировки, или Т-бита. Если
этот бит установлен, то после выполнения каждой инструкции, включая и
1 В переводной литературе в данном случае используются также термины: програм-
мные прерывания, внутренние прерывания. — Прим, перев.
218
1 000034 TRAPPC = 34 ;TRAP-BEKTOP (PC
2 3 4 000000 000036 172335 ВАТА: TRAPPS .WORD = 36 172335 ; И PSW)
35 000556 012767 START: MOV SABS,TRAPPC ОСТАНОВИТЬ АДРЕС ПОДПРОГРАММЫ
36 000564 001226’ 000034 005067 CLR TRAPPS ; И PSW
57 000614 58 000616 000036 104400 000000’ TRAP .WORD DATA ПРЕРЫВАНИЕ НА ПОДПРОГРАММУ ’ABS ;адрес ЧИСЛА
59 000620 91 001226 017646 ABS: MOV (•(SP) ,-(SP) ;(СЛЕДУЮЩАЯ ИНСТРУКЦИЯ) ПОМЕСТИТЬ АДРЕС В СТЕК
92 001232 000000 005776 TST 8(SP) ПРОВЕРИТЬ ЗНАК
93 001236 000000 100002 BPL RETURN .НЕОТРИЦАТЕЛЬНОЕ — ХОРОШО
94 001240 005476 NEG 8(SP> ; ИНАЧЕ ИЗМЕНИТЬ ЗНАК
95 001244 000000 005726 RETURN: TST (SP) + ;УВЕЛИЧИТЬ SP НА 2
96 001246 062716 ADD 42» (SP) ПОДСТРОИТЬ АДРЕС ВОЗВРАТА
97 001252 000002 000006 RTT ВОССТАНОВИТЬ PC И PSU
Рис. 11.3.1
инструкцию RTI, но исключая инструкцию RTT, будет осуществляться пере-
ход к ловушке по вектору в ячейках 000014 — 000016. Но Т-бит обычно ис-
пользуется только поставляемыми с операционной системой отладочными
средствами, и поэтому различие между RTT и RTI в этой книге остается без
всяких последствий.)
Поскольку инструкция TRAP структурно совпадает с гипотетической ин-
струкцией JTZ, за исключением того, что в стек помещается содержимое
PSW, мы можем использовать TRAP так же, как JTZ в предыдущем разделе.
На рис. 11.3.1 показан листинг программного сегмента, представляющего со-
бой упрощенный вариант последнего примера из предыдущего раздела с за-
меной JTZ на TRAP. Есть также еще два небольших, но заслуживающих вни-
мания изменения. Вновь (в строках 1 и 2) мы использовали оператор пря-
мого присваивания для присвоения значений 34 и 36 символам TRAPPC и
TRAPPS. (Напоминаем читателю, что ни в коей мере это не является необхо-
димым. Мы могли бы также использовать 34 и 36, но символьные имена про-
сто несколько улучшают читаемость листинга.) В строке 35 адрес подпро-
граммы ABS перемещается в ячейку 000034 для установки слова PC в TRAP-
векторе. Теперь возникает вопрос, что делать с ячейкой 000036 — словом
PSW в TRAP-векторе. Вспоминаем, что, когда происходит переход к ловуш-
ке, это слово становится новым содержимым PSW. Трудно предвидеть, как
конкретное значение этого слова повлияет на обработку в подпрограмме
ABS после перехода в нее с помощью инструкции TRAP, и поэтому мы скло-
няемся к тому, чтобы просто игнорировать ячейку 000036 = TRAPPS. Пусть
ее значение будет таким, какое получится, и мы не станем заботиться о за-
грузке в нее чего-либо определенного. По ряду причин, большинство из ко-
торых не будут понятны до следующей главы, лучше, чтобы при выполнении
подпрограммы ABS, переход в которую осуществляется с помощью TRAP,
219
мы знали, каким в этот момент будет содержимое PSW, поскольку PSW со-
держит некоторые биты, помимо кодов условий, влияющие на действия, вы-
полняемые ЦП. В тех случаях, которые мы здесь рассматриваем, почти все-
гда безопасно помещать в это слово значение 0, поэтому в строке 36 второе
слово TRAP-вектора очищается. Осталось проделать единственную модифи-
кацию — заменить оператор возврата в подпрограмме ABS. Инструкция MOV
(SP)+, PC больше работать не будет, поскольку не восстановит содержимое
PSW, т. е. содержимое PSW из основной части программы останется в стеке.
Поэтому вместо нее мы используем RTT (или RTI). Если не считать этих из-
менений, то программа продолжает работать точно так же, как и прежде, по-
скольку, как и в случае JTZ, возвратное значение PC оказывается на верши-
не стека.
11.4. ПЕРЕХОД С ПОМОЩЬЮ ИНСТРУКЦИИ TRAP К НЕСКОЛЬКИМ ТОЧКАМ
ВХОДА
В разд. 11.2 мы говорили о том, что действие инструкции JTZ всегда со-
стоит в придании PC значения содержимого ячейки 000000. И если JTZ ис-
пользуется для передачи управления к более чем одной точке входа в подпро-
грамму, то содержимое ячейки 000000 должно модифицироваться перед каж-
дым выполнением JTZ. Поскольку архитектура инструкции TRAP несуще-
ственно отличается от архитектуры JTZ и, следовательно, не может решить
эту проблему, необходимо дать некоторые комментарии относительно TRAP.
Фактически, используя TRAP и выполнив некоторое дополнительное програм-
мирование, можно передать управление к нескольким различным точкам
входа без изменения слова PC в TRAP-векторе.
Операционный код инструкции TRAP есть 104400, или в двоичном виде:
1 000 100 1 00 000 000
Младший байт, 00 000 000, фактически не используется ЦП. Иными словами,
старший байт содержит достаточно информации, чтобы декодер распознал
инструкцию TRAP; после этого содержимое младшего байта он просто игно-
рирует. Следовательно, все операционные коды — 104400, 104401, 104477,
104720 и т. п. — представляют инструкцию TRAP. Если ассемблер получает
для ассемблирования инструкцию TRAP, то, как мы ожидаем, он генерирует
код 104400. Однако, если ему задается ассемблирование инструкции TRAP п,
где п — это целое (без знака) , имеющее значение между 0 и 377 включитель-
но, то он генерирует слово, старший байт которого содержит код инструкции
TRAP, а младший байт содержит целое число п. Таким образом, например,
TRAP 12 будет иметь код 104412, тогда как TRAP 104 будет ассемблировано
как 104504. (Следует помнить, что все это — представления инструкции
TRAP.) Значение для программиста числа в младшем байте видно из следую-
щего примера, листинг которого показан на рис. 11.4.1 и который содержит
некоторые особенности, заслуживающие обсуждения.
В этом примере мы полагаем, что программный сегмент содержит четыре
подпрограммы, точки входа которых имеют метки MODI, MOD2, MOD3 и
MOD4. Мы хотим иметь возможность войти в любой из этих модулей с по-
мощью инструкции TRAP, но без модификации первого слова (PC) TRAP-
вектора. В строке 14 адрес XFEP (001016) помещается в первое слово TRAP-
220
1 2 000034 000036 TRAPPC = 34 TRAPPS - 36 HRAP-BEKTOP (PC ; и psh>
14 000142 012767 START! 001016’ 000034 NOV SXFER.TRAPPC ;УСТАНОВИТЬ PC В TRAP-BEKTOPE
15 000150 005067 000036 CLR TRAPPS ; И ЕГО PSW
49 000436 50 000440 104404 TRAP 4 ;trap с КОДОК 4 ;(СЛЕДУОШАЯ ИНСТРУКЦИЯ)
64 000512 65 000514 104401 TRAP 1 (TRAP С КОДОМ 1 И С ЛЕ ДУМАЯ ИНСТРУКЦИЯ)
88 89 .ЭТА ПОДПРОГРАММА ПЕРЕДАЕТ УПРАВЛЕНИЕ' К УКАЗАННОМУ МОДУЛИ 5
90 001016 011603 XFER: MOV (SP)»R3 ?ПОЛУЧИТЬ АДРЕС ВОЗВРАТА
91 001020 116303 MOVB -2(R3),R3 1 ПОЛУЧИТЬ TRAP-КОД
92 001024 177776 006303 ASL R3 t И УДВОИТЬ ЕГО
93 001026 000173 JMP 8DSPTCH-2(R3) гПЕРЕИТИ ’ЧЕРЕЗ ВЕКТОР
94 f 126 127 128 001112 001110’ ; к указанной программе ?ВЕКТОР ДЛЯ ПЕРЕДАЧИ УПРАВЛЕНИЯ К ПРОГРАММАМ 001266’ DSPTCH: .WORD MODI
129 001114 001344’ .WORD M0D2
130 001116 001410’ .WORD M0D3
131 001120 001436’ .WORD M0D4
153 001266 MODI: ПОЧКА ВХОДА В МОДУЛЬ «1
167 001342 000006 RTT ;возврат
168 1
169 001344 M0D2: ПОЧКА ВХОДА В МОДУЛЬ 02
181 001406 000006 RTT ;возврат
182 ;
183 001410 M0D3: ;ТОЧКА ВХОДА В МОДУЛЬ #3
195 001434 000006 RTT .'ВОЗВРАТ
196 s
197 001436 M0D4: ПОЧКА ВХОДА В МОДУЛЬ 44
212 001470 000006 RTT •ВОЗВРАТ
Рис. 11.4.1
вектора (ячейку 000034) , а второе слово этого вектора очищается в строке
15, поскольку .ничего лучшего мы сделать с ним не можем. Проследим де-
тально действие инструкции TRAP 4 в строке 49.
Когда PC содержит 000436, ЦП извлекает инструкцию TRAP (а именно,
104404) и увеличивает с (PC) до 000440. Инструкция TRAP помещает в стек
содержимое PSW (каким бы оно ни было) и содержимое PC (000440), поме-
щает содержимое ячейки 000034 (001016) в PC, а содержимое ячейки000036
(000000) — в PSW. Таким образом, выполнение возобновляется с ячейки,
имеющей метку XFER. Инструкция MOV (SP) , R3 пересылает (но не вытал-
221
кивает) содержимое слова на вершине стека в R3 так, что c(R3) становится
равным 000440. Источником инструкции MOVB —2(R3) , R3 является содер-
жимое байта памяти, адрес которого равен c(R3) — 2 = 000440 — 2 = 000436,
т. е. младшему байту инструкции TRAP 4. Поскольку этот байт содержит чис-
ло 004 и поскольку приемником инструкции MOVB является R3, к моменту
полного завершения инструкции c(R3) будет равно 000004. Следующая ин-
струкция (в строке 92) удваивает это значение, так что теперь c(R3) равно
000010. В инструкции JMP в строке 93 адрес ЭЯ (приемника) вычисляется
следующим образом. Индекс, DSPTCH-2 (001110), прибавляется к c(R3)
(000010) и результатом оказывается значение 001120. Поскольку в инструк-
ции JMP используется косвенная адресация (@), адресом приемника будет
содержимое ячейки 001120, а именно 001436 = M0D4. Таким образом, число
4, которое мы назовем TRAP-кодом, привело к передаче управления подпро-
грамме, точка входа которой является четвертым элементом в векторе
DSPTCH — так называемом векторе диспетчеризации. Выполнение подпро-
граммы MOD4 завершается инструкцией RTT. Верхний элемент стека, а имен-
но число 000440, выталкивается из стека в PC, так же как и ’’старое” содер-
жимое PSW, и, таким образом, выполнение программы возобновляется с
ячейки 000440, т. е. со следующей инструкции в стержневой ветви програм-
мы. Читатель может аналогично проанализировать инструкцию TRAP 1 в
строке 64.
Хотя видно, что этот программный сегмент выполняет то, для чего пред-
назначался, и содержит некоторые интересные и новые виды программиро-
вания, остается вопрос о том, почему во всех случаях использовалась ин-
струкция TRAP, тогда как доступна намного более удобная инструкция
JSR. Мы не можем дать исчерпывающе удовлетворительный ответ на этот
вопрос; можно только сказать, что на машинах, имеющих этот тип инструк-
ции-ловушки, она нередко Используется для получения преимуществ при си-
стемном программировании, т. е. при написании различных модулей, состав-
ляющих операционную систему. Короче говоря, инструкция TRAP никогда
и не предназначалась для обычного каждодневного программирования.
11.5. АЛЬТЕРНАТИВНЫЙ НАБОР РЕГИСТРОВ ОБЩЕГО НАЗНАЧЕНИЯ
Вероятно, к настоящему моменту у читателя создалось впечатление, что
инструкция TRAP весьма своеобразна. Возможно, наиболее загадочным ас-
пектом является сохранение содержимого PSW (и его последующее восста-
новление при выполнении инструкции RTT или RTI). До сих пор мы не нахо-
дили ничего лучшего, что бы сделать с PSW, которое извлекается из ячейки
000036, кроме того, что очистить эту ячейку в ноль. В этом разделе мы пред-
лагаем пример, показывающий, что, действительно, может оказаться полез-
ным получение нового содержимого PSW при выполнении инструкции TRAP
и затем восстановление первоначального значения PSW при возвращении к
той части программы, которая выполняет инструкцию TRAP. Мы понимаем,
что для оправдания действий, осуществляемых комбинацией TRAP/RTT, —
помещение в стек и извлечение из него содержимого PSW— этот пример не-
сколько слабоват. Однако здесь вводится другая идея, представляющая зна-
чительный интерес для некоторых машин и в некоторых средах.
222
1 000034 TRAPPC = 34
2 3 4 000036 177776 TRAPPS = 36 PSW = 177776
5 000000 012767 000226’ 000034 START: MOV 1ЕЫТРТ»TRAPPC ;УСТАНОВИТЬ PC В TRAP-BEKTOPE
6 7 000006 012767 •004000 000036 MOV 44000.TRAPPS :УСТАНОВИТЬ БИТ И В PSW • TRAP-BEKTOPA
8 9 41 42 74 75 76 000014 005067 177776 000116 104400 000120 000226 CLR PSW ;ГАРАНТИРОВАТЬ ИСПОЛЬЗОВАНИЕ ; НУЛЕВОГО НАБОРА ; ОБЩЕДОСТУПНЫХ РЕГИСТРОВ TRAP !ПЕРЕХОД К ’ENTPT’ !(СЛЕДУЮЩАЯ ИНСТРУКЦИЯ) ENTPTs • ;ТОЧКА ВХОДА В ПРОГРАММУ ;ПРОГРАММА ИСПОЛЬЗУЕТ 1-И НАБОР ! ОБЩЕДОСТУПНЫХ РЕГИСТРОВ
93 94 95 000252 000006 RTT 1ВОССТАНОВИТЬ PC И PSU МВОЗВРАЫАЕТ К НУЛЕВОМУ НАБОРУ 5 ОБЩЕДОСТУПНЫХ РЕГИСТРОВ)
Рис. 11.5.1
До сих пор везде мы говорили о наборе регистров общего назначения от R0
до R5. На некоторых моделях ЭВМ PDP-11 фактически имеются два набора
таких регистров. Однако никакого нотационного различия между ними нет:
независимо от того, какой набор имеется в виду, третий, например, регистр,
называется R2. Но как тогда ЦП узнает, содержимое какого регистра R4
должно быть увеличено при выполнении инструкции, подобной INC R4 — с
операционным кодом 005204? Ответ определяется битом 11 в PSW. Если
этот бит установлен, то ЦП выбирает один из наборов регистров общего на-
значения; если он сброшен, ЦП выбирает Другой набор. Таким образом, при
обработке инструкции, включающей в себя какой-либо регистр общего на-
значения, ЦП сначала проверяет PSW, чтобы определить, к какому регистру
производится обращение — к регистру из набора 0 или из набора 1. (Нефор-
мально мы именуем наборы регистров 0 и 1, что соответствует состояниям
бита 11 в PSW.)
Программный сегмент на рис. 11.5.1 иллюстрирует, как программист мо-
жет получить преимущество от этого альтернативного набора регистров при
использовании инструкции TRAP для передачи управления подпрограмме, на-
зываемой в данном случае ENTPT. После того как адрес ENTPT помещен в
ячейку 000034 для обеспечения перехода к подпрограмме, второе слово
TRAP-вектора — слово PSW — устанавливается в 004000. При этом бит 11
устанавливается в 1, а остальные биты — в 0. Следующая инструкция (в стро-
ке 8) устанавливает текущее значение содержимого PSW в 0. Единственное
назначение этой инструкции — гарантировать, что бит 11 в PSW (бит набора
регистров) является нулевым, так что основная программа будет работать
с нулевым набором регистров общего назначения. Теперь начинается основ-
ная обработка, при которой предположительно используется какое-то число
регистров из набора 0. Когда в строке 41 встречается инструкция TRAP, уп-
равление передается по адресу с меткой ENTPT, текущие значения содержи-
223
мого PSW и PC помещаются в стек, а новые их значения получаются из TRAP-
вектора. К моменту начала обработки Ц программе ENTPT PSW будет содер-
жать значение, в котором бит 11 равен 1, и, следовательно, ENTPT будет ра-
ботать с набором 1 регистров общего назначения. Теперь любые регистры от
R0 до R5 могут использоваться без сохранения и восстановления их содержи-
мого, поскольку ENTPT работает с набором регистров, отличающимся от
того, который используется в стержневой ветви; следовательно, чтобы ни де-
лалось в ENTPT, на значения содержимого этих регистров из стержневой вет-
ви это никак не повлияет. Когда встретится инструкция RTT, первоначальное
(из стержневой ветви) значение PSW будет восстановлено и обработка про-
должится с набором 0 регистров общего назначения, а значит, и с теми значе-
ниями этих регистров, которые они имели в стержневой ветви. Таким обра-
зом, на моделях, где реализована эта особенность, инструкция TRAP может
использоваться для переключения двух наборов регистров общего назначе-
ния. В действительности же имеются более легкие способы переключения на-
боров регистров, но в повседневном программировании свопинг наборов ре-
гистров используется редко.
11.6. ЕЩЕ РАЗ О КОМПОНОВЩИКЕ
Этот раздел может быть опущен, поскольку в нем рассматриваются вопросы, не влия-
ющие на последующий материал и на способность пользователя ассемблировать, компо-
новать и выполнять программы. В то же время читатель, стремящийся к более глубоко-
му пониманию процесса загрузки-редактирования-компоновки, может счесть полезным
посвятить какое-то время рассматриваемой здесь теме.
Восприимчивый читатель, возможно, заметил кажущуюся неправильность вычислен-
ных смещений, сгенерированных ассемблером в каждом листинге подпрограммы этой
главы. Рассмотрим, в частности, листинг программного сегмента из разд. 11.3 (см.
рис. 11.3.1). Строка 35 там выглядит так:
000556 012767, START: MOV #ABS, TRAPPC
000560 001226'
000562 000034
000564 (следующая инструкция)
Инструкция MOV правильно ассемблирована как 012767, и это показывает, что ссылка
на источник производится в режиме адресации 2 с PC, т. е. в непосредственном режиме,
и, значит, в приемник должно быть переслано чувствительное к перемещению слово
001226. Приемник определяется режимом адресации 6 с PC (относительно PC), и поэто-
му адрес приемника вычисляется следующим образом. Мы берем текущее содержимое
PC, которое при выполнении будет равно 000564, и прибавляем его к смещению 000034.
В результате получаем адрес приемника 0005 64 + 000034 = 000620. Но вряд ли это пра-
вильно, поскольку приемником этой инструкции предполагается 000034 — адрес перво-
го слова TRAP-вектора. Предположим, с другой стороны, что ассемблер все-таки пра-
вильно вычислил смещение: 000564 + смещение = 000034, так что оно получилось рав-
ным 177250, а инструкция MOV ассемблирована так:
000556 012767 START: MOV #ABS, TRAPPC
000560 001226'
000562 177250
00564 (следующая инструкция)
Хотя результат кажется правильным, тем не менее это не так по следующей причине.
Предположим, что представленный этим листингом объектный модуль загружается те-
224
перь, начиная с ячейки памяти 001000. Тогда, как показано, слово 177250 в ячейке
000562 будет переслано в ячейку 001562. При указанном смещении 177250 произойдет
обращение к ячейке памяти 001034, а не 000034, как это было бы нужно.
Проблема вполне ясна. Ссылка на абсолютный (т. е. фиксированный) адрес была сде-
лана из модуля, который может подвергаться перемещению, причем ссылка была сде-
лана с помощью смещения к этому абсолютному адресу. Как мы видели в гл. 9, когда
перемещаемый модуль ссылается с помощью смещения на какой-то адрес внутри этого
модуля, который также передвигается при перемещении, то смещение не меняется, и
поэтому никакой подстройки не требуется. Но если обращение делается к фиксирован-
ному адресу (адресу, который никогда не передвигается) в модуле, который позднее
перемещается, то смещение к этому фиксированному адресу должно быть подправлено,
когда осуществляющий обращение оператор окажется перемещенным. Если проблема
ясна, то решение ее не очень понятно, хотя, тем не менее, и возможно.
При генерации объектного файла из такого исходного файла, который мы здесь рас-
сматриваем, ассемблер распознает те ссылки, которые делаются к абсолютным адресам
в режиме адресации 6 (или 7) с PC. Из-за потенциальной проблемы при перемещении
объектного модуля ассемблер не может правильна вычислить смещение, которое было
бы действительным при перемещении. Вместо этого он помещает сам абсолютный ад-
рес (в нашем случае 000034) туда, где должно быть слово смещения, и записывает, что
он произвел такую операцию с этим словом (т. е. с 000562) в еще одной таблице, назы-
ваемой таблицей абсолютных ссылок, которая строится как часть объектного файла.
В фазе редактирования процесса загрузки-редактирования-компоновки редактор мо-
жет найти уакие абсолютные ссылки и, поскольку он знает ФАЗ и, следовательно, физи-
ческий адрес такой абсолютной ссылки и абсолютный адрес, на который делается ссыл-
ка, он легко может вычислить соответствующее смещение и вставить его в загрузочный
модуль, перекрывая абсолютный адрес, помещенный туда ассемблером. Таким образом,
если бы интересующий нас модуль загружался, например, начиная с ячейки 001000, то
при абсолютной ссылке на 000034, имеющей место в 001562, редактор использовал бы
предполагаемое значение PC 001564 и вычислил бы смещение к 000034: 000034 -
— 001564 = 176214. Это число было бы помещено как абсолютная ссылка в 001562 в
зугрузочном модуле, и при выполнении инструкции происходило бы правильное обра-
щение к ячейке 000034.
В заключение отметим, что если бы инструкция MOV #ABS, TRAPPC была записана
как MOV #ABS, @#TRAPPC, которая была бы ассемблирована следующим образом:
000556 012737 START: MOV #ABS, @#TRAPPC
000560 001226'
000562 000034
000564 (следующая инструкция)
то все записи о ячейках и последующие подстройки компоновщиком были бы ненужны-
ми, поскольку при абсолютном режиме адресации с PC абсолютный адрес 000034 не тре-
бовал бы подстройки при перемещении объектного модуля.
11.7. УПРАЖНЕНИЯ
11.1.1. Какими будут последствия выполнения инструкции перехода к подпрограм-
ме, которая всегда сохраняет содержимое PC в некоторой фиксированной ячейке (та-
кой, как, например, 000000), как это предполагалось в разд. 11.1? В частности, как бу-
дут обрабатываться вызванные подпрограммы, которые обращаются к другим подпро-
граммам?
11.2.1. Гипотетическая инструкция JTZ была описана так, что она помещает в стек
8 Зак. 2212
c(PC) и затем загружает в PC содержимое ячейки памяти 000000. Является ли пара ин-
струкций
MOV PC, -(SP)
MOV 0, PC
точной копией JTZ? Дайте пояснения.
11.2.2. Чем отличается гипотетическая инструкция JTZ от конструкции JSR PC, @0?
11.2.3. Сможет ли инструкция RTS PC выполнить возврат из подпрограммы, входе
которую осуществлялся с помощью инструкции JTZ?
11.3.1. Существуют три подобные TRAP инструкции: EMT, ВРТ и ЮТ (из которых
ЕМТ почти идентична ей). Найдите их описания в приложении А и сравните их с TRAP.
11.3.2. Объясните, почему инструкция TRAP точно эмулируется или не эмулируется
следующей последовательностью инструкций:
MOV PSW, -(SP)
MOV 36, PSW
MOV PC, -(SP)
MOV 34, PC
11.3.3. Объясните, почему инструкция TRAP точно эмулируется или не эмулируется
следующей последовательностью инструкций:
MOV PSW, -(SP)
MOV 36, PSW
JSR PC, @34
11.3.4. Объясните, почему инструкция RTT точно эмулируется или не эмулируется
следующими инструкциями:
MOV 2(SP), PSW
MOV (SP)+,(SP)
MOV (SP)+,PC
11.3.5. Внимательно проследив за активностью стека в строках от 66 до 73, покажи-
те, что программный сегмент на рис. 11.7.1 успешно передает управление с помощью
инструкции TRAP подпрограмме МАХ (которая вычисляет максимальное из заданного
набора чисел), оставляет максимум в стеке и правильно осуществляет возврат к основ-
ной части программы (с помощью инструкции RTT).
11.3.6. Контролируя состояние стека в процессе выполнения рекурсивных инструк-
ций TRAP в строке 18, покажите, что при выполнении программы на рис. 11.7.2 (деся-
тичное) распечатываемое число будет равно 720 (6!).
11.3.7. Предположим, что с (000034) = SUB, где SUB - это точка входа в некоторую-
подпрограмму, выход из которой осуществляется с помощью инструкции RTT. Когда,
будет выполняться следующий программный сегмент, можно ли определить, действи-
тельно ли сгенерирует ветвление к NEXT инструкция условного ветвления BEQ? Дайте
пояснения.
CLR R4
TRAP
BEQ NEXT
Как эта ситуация отличается от следующей (если вообще отличается) :
CLR R4
JSR PC, SUB
BEQ NEXT
1 2 000034 000036 TRAPPC = 34 TRAPPS = 36 ;АДРЕС PC В TRAP-BEKTOPE ;АДРЕС PSW В TRAP-BEKTOPE
25 001232 26 DATA» !(ЗДЕСЬ НАХОДЯТСЯ ЧИСЛА» СРЕДИ ; КОТОРЫХ МНЕТСЯ МАКСИМУМ)
38 001730 012767 003024’ 000034 STARTs MOV ОМАХ»TRAPPC ;УСТАНОВИТЬ PC В TRAP-BEKTOPE
39 001736 40 001742 005067 000036 104400 CLR TRAPPS TRAP ? И ЕГО PSW ;ВЫПОЛНИТЬ TRAP
41 001744 000005 .WORD 5 7СЧЕТЧИК ЧИСЕЛ
42 001746 43 001750 44 001232’ .WORD DATA ; И АДРЕС 1(СЛЕДУВНАЯ ИНСТРУКЦИЯ ; ОСНОВНОЙ ПРОГРАММЫ)
66 003024 011646 MAXs MOV (SP),-(SP) ;ЗАНЕСТИ PC И PSW ИЗ ОСНОВНОЙ
67 003026 016666 000004 000002 MOV 4(SP),2(SP) 1 ПРОГРАММЫ В СТЕК
68 003034 010046 MOV RO»-(SP) ;СОХРАНИТЬ РЕГИСТРЫ
69 003036 010246 MOV R2,-(SP) : R0 И R2
70 003040 017600 000004 MOV 84(SP),R0 »ПОЛУЧИТЬ СЧЕТЧИК ЧИСЕЛ
71 003044 062766 000002 000004 ADD »2»4<SP) " И ПОДСТРОИТЬ PC ИЗ ОСНОВНОЙ ПРОГРАММЫ
72 003052 017602 000004 MOV 04<SP)»R2 ;ПОЛУЧИТЬ АДРЕС ДАННЫХ
73 003056 062766 000002 000004 ADD 02»4<SP) ? И ПОДСТРОИТЬ PC ИЗ ОСНОВНОЙ ПРОГРАММЫ
74 003064 005300 DEC RO ;ПОДСТРОИТЬ СЧЕТЧИК ЧИСЕЛ
75 003066 76 012266 000010 MOV (R2) + ,10(SP) ;ПОМЕСТИТЬ ПЕРВОЕ ЧИСЛО В СТЕК ; КАК (ВРЕМЕННЫЙ) МАКСИМУМ
77 003072 026622 000010 LOOP: CMP 10<SP),(R2)+ ;СРАВНИТЬ СО СЛЕДУЮЩИМ ЧИСЛОМ
78 003076 002003 BGE NEXT ;ВОЛЬВЕ — ОБОЙТИ
79 003100 016266 177776 000010 MOV -2(R2)»10(SP) ;ЗАМЕНИТЬ ВРЕМЕННЫЙ МАКСИМУМ
80 003106 077007 NEXT: SOB RO»LOOP ОБРАБОТАТЬ СЛЕДУМЕЕ ЧИСЛО
81 003110 012602 MOV (SP) + »R2 ^ВОССТАНОВИТЬ РЕГИСТРЫ
82 003112 83 003114 012600 000006 MOV (SP)+,RO RTT ; R0 И R2 ’;ВОЗВРАТ ИЗ TRAP
Рис. 11.7.1
где SUB — подпрограмма, возврат из которой осуществляется с помощью инструкции
RTS PC?
11.4.1. Обращаясь к программе на рис. 11.4.1, мы видим, что в принципе можно иметь
большое количество (вероятно, сотни) точек входа в подпрограммы в векторе диспет-
черизации DSPTCH, т. е. в подпрограммы, доступные через программный сегмент XFER,
который передает управление заданной точке входа на основе TRAP-кода - целого чис-
ла в младшем байте инструкции TRAP.
а) Покажите, что программный сегмент XFERb том виде, как он написан, не будет
правильно обрабатывать такие обращения, как TRAP 206, или, в общем случае, TRAP п,
где п больше, чем 1778.
б) Как можно исправить недостаток, обнаруженный в п. а) ?
в) Каким является ограничение на число подпрограмм, которым может быть переда-
но управление с помощью схемы из этого примера программирования?
226
8*
227
1 000034 TRAPPC = 34
2 000036 TRAPPS > 36
3
4 000000 ANSWER: .BLKW 1
3
6 000002 012767 000070’ 000034 START: MOV «RECURS.TRAPPC
7 000010 005067 000036 CLR TRAPPS
8 000014 012700 000006 MOV t6.R0
9 000020 012701 000001 MOV 41.R1
10 000024 104400 TRAP
И 000026 010167 177746 MOV Rl.ANSWER
12 000032 «OUT.DEC «ANSWER.«1
13 000066 xEXIT
14 J
15 000070 070100 RECURS: MUL R0.R1
16 000072 005300 DEC R0
17 000074 001401 DEO RETURN
18 000076 104400 TRAP
19 000100 000006 RETURN: RTT
20
21 000002’ .END START
Рис. 11.7.2
г) В подпрограмме XFER существует еще один небольшой недостаток, а именно, ис-
пользование регистра R3 без сохранения и восстановления его содержимого для опреде-
ления смещения от начала вектора диспетчеризации DSPTCH, который используется в
инструкции JMP в строке 93 (см. рис. 11.4.1). Как это можно исправить (если это воз-
можно) ? Можно ли сохранить и восстановить содержимое R3 позднее? Можно ли выпол-
нить инструкцию JMP без использования какого-либо регистра общего назначения?
11.5.1. В разд. 11.5 мы видели, что инструкция TRAP может использоваться для из-
менения набора регистров общего назначения, поскольку она получает новое содержи-
мое PSW H3 ячейки 000036. Объясните, насколько это усложняет передачу аргументов
подпрограмме, передача управления к которой происходит с помощью инструкции TRAP.
11.5.2. Каково (если оно есть) различие между MOV #4000, TRAPPS и BIS #4000,
TRAPPS? (См. листинг программы на рис. 11.5.1.) Эта инструкции, конечно, не идентич-
ны, но есть ли какая-нибудь разница в их чистом действии, когда нас интересует данный
программный сегмент?
11.5.3. Каким будет эффект от переключения наборов регистров общего назначения,
как это описано в разд. 11.5, если инструкция TRAP будет использоваться при рекурсив-
ных или вложенных обращениях?
113.4. Предложите метод изменения набора регистров общего назначения, не требу-
ющий замены содержимого PSW (как в случае TRAP), при котором, следовательно, мо-
гут использоваться подпрограммы типа JSR.
11.6.1. Как ассемблер может распознать ссылку на абсолютное число? Ведь он дол-
жен быть в состоянии сделать это, чтобы построить таблицу абсолютных ссылок.
11.6.2. Почему конструкция @# (например, CRL@#TRAPPS) не требует подстройки
компоновщиком?
11.6.3. Если АЗА перемещаемого объектного модуля есть 000000 и инструкция
JSR РС,@0
находится в ячейке 000464, то как будет ассемблирована эта инструкция? Если ФАЗ
модуля в конце концов будет равен 003442, то как код, сгенерированный для инструк-
ции JSR, будет модифицирован компоновщиком (если вообще будет модифицирован)?
ГЛАВА 12. ПРЕРЫВАНИЯ И ОБРАБОТКА ВВОДА-ВЫВОДА
12.1. ВВЕДЕНИЕ
Предыдущую главу мы посвятили изучению инструкции TRAP, и читатель
мог удивиться, почему тск много времени мы потратили на инструкцию, ко-
торая по нашему собственному утверждению не часто используется в повсе-
дневном программировании. Возможно, одним из наиболее загадочных ас-
пектов этой инструкции является сохранение содержимого PSW и получение
нового содержимого PSW из ячейки 000036. Кажется, что не видно необходи-
мости сохранять и восстанавливать состояние процессора, за исключением
лишь одного примера, в котором мы использовали особенность переключе-
ния наборов регистров, т. е. задачи, которая совершенно легко может быть
решена другими способами. И если сохранение PSW не приводит к заметным
последствиям, то инструкция TRAP может быть заменена на JSR PC, ХХХ —
намного более гибкую инструкцию, с которой легче обращаться.
Действительно, конкретная инструкция TRAP (или близкие ей ЕМТ,ЮТ
и т. п.) не представляют для нас особого интереса. Скорее, нас интересует
концепция ловушки, когда при некоторых обстоятельствах содержимое PSW
и PC помещается в стек, а новое содержимое PC и PSW берется из некоторых
фиксированных ячеек оперативной памяти. Как мы видели, эти ’’некоторые
обстоятельства” могут явиться результатом выполнения в программе ин-
струкции TRAP. Они могут также появиться в результате какого-то внешне-
го события, которым мы (вернее сказать, наша программа), не может управ-
лять, но которое принуждает ЦП выполнить инструкцию типа TRAP во время
выполнения нашей программы. Как будет видно дальше в этой главе, такие
внешние события действительно происходят относительно часто. Вскоре
станет очевидным, что последовательность действия ’’сохранить текущее
c(PSW) — извлечь новое c(PSW) — восстановить старое c(PSW) оказывает-
ся абсолютно необходимой для правильного выполнения наших программ.
Таким образом, мы увидим, что PSW, которое в предыдущей главе не играло
почти никакой роли, имеет очень большое значение. Изучение инструкции
TRAP дало нам некоторые важные основы, которые послужат также и при
изучении многочисленных явлений, связанных с аппаратными прерываниями.
12.2. ПРЕРЫВ АНИЯ
Чтобы вникнуть в те типы прерываний, с которыми сталкиваются вычис-
лительные системы, и способы, которыми они их обрабатывают, рассмотрим
некоторые прерывания, с которыми справляются люди в своей повседнев-
ной жизни. Здесь есть ряд интересных аналогий. Каждый день прерывания
нас буквально бомбардируют. Простое действие — переход из одной аудито-
рии в другую — зачастую прерывается, когда приходится остановиться, что-
бы потолковать с приятелем. Когда мы ведем автомобиль, то наше движение
может быть прервано изменением светофора с зеленого на красный или со-
шедшим с тротуара пешеходом. Сначала мы познакомимся с рядом событий
из реальной жизни, аналогия с которыми определит направление наших иссле-
дований событий в вычислительных машинах и аппаратуре и соответствующе-
го программирования, необходимого для их эффективной обработки.
228
229
Сценарий 1. Предположим, что мы сидим дома в гостиной и читаем,
когда у двери с черного хода лает собака, прося ее впустить. Мы кладем кни-
гу, идем к черному ходу, впускаем собаку, возвращаемся в гостиную, берем
книгу и продолжаем чтение.
Без сомнения, это обычное и весьма простое событие; оно является при-
мером прерывания, и мы обрабатываем его очевидным образом. Краткий
анализ этой ситуации показывает, что было выполнено изрядное число дей-
ствий, но, поскольку с подобными ситуациями мы имеем дело очень часто,
процессы, необходимые для успешной обработки этого простого типа преры-
вания, уже не лежат в области нашего сознания — мы справляемся с таким
прерыванием без того, чтобы его обдумывать.
Во-первых, мы должны быть уверены, что произошло прерывание, в чем
мы убеждаемся, услышав лай собаки. (Если бы пес просто подошел к черно-
му ходу и уселся там, то маловероятно, что мы заметили бы его до тех пор,
пока позднее, вероятно, не стали бы его сознательно разыскивать.) Затем мы
должны распознать, что звук производит лающая собака, а не телефон или
дверной звонок. (Это не столь глупо, как звучит. В действительности совер-
шается некоторый тип процесса определения источника прерывания, что-
бы можно было правильно обработать его — ведь когда лает пес, не стоит сни-
мать телефонную трубку.) Мы также должны распознать, что лай принадле-
жит нашему псу и что он доносится с черного хода, а не от парадной двери.
Наконец, мы должны как-то отличить лай собаки с просьбой ’’впустите меня”
от других типов лая.
Определив природу этого прерывания, мы видим,что располагаем хорошо
определенной процедурой, чтобы, с ним справиться. Однако перед реализаци-
ей этой процедуры мы отмечаем, что мы делали в момент, когда случилось
прерывание, т. е. мы отмечаем (в уме), что в момент прерывания мы читали
книгу в гостиной. Мы даже можем положить в книгу закладку, которая по-
может в дальнейшем продолжить чтение. Без такой отметки мы, конечно,
сможем впустить собаку, но будем не в состоянии продолжить то, что делали.
Таким образом, мы кладем книгу, идем к черному ходу, впускаем собаку и
затем задаемся вопросом о том, на чем же мы остановились. Взгляд (мыслен-
ный) на наши заметки показывает, что, когда случилось прерывание, мы чи-
тали; тогда мы возвращаемся в гостиную, берем книгу и продолжаем чтение
с того места, где мы его приостановили.
Читатель может выразить недовольство, что мы слишком уж углубились
в анализ ситуации, настолько простой, что она едва ли заслуживает, чтобы на
нее тратить время. Но процессы, через которые мы прошли в этом примере,
весьма и весьма существенны, и будет разумно суммировать их следующим
образом:
1. Процесс выполнения нами чего-то (чтения) был прерван.
2. Мы определили, что вызвало прерывание (лай собаки у двери с черного
хода), и определили, что надо делать.
3. Мы отметили, что мы делали в момент прерывания.
4. Мы обработали прерывание (впустили собаку).
5. Мы посмотрели на наши отметки, чтобы определить, как вернуться к
тому, чем мы были заняты в момент прерывания.
6. Мы вернулись к тому, что делали, когда произошло прерывание (к чте-
нию) .
230
Чтение
Диаграмма, поясняющая эту ситуацию,
показана на рис. 12.2.1 •
В остальных примерах столь деталь-
ного анализа мы проводить не будем.
Но читатель должен знать, что все эти
процессы играют свою роль всякий раз,
когда мы имеем дело с прерыванием ка-
кого-то типа, даже если большая часть
из них лежит на уровне подсознания.
Тем не менее почти все каждодневные
прерывания, с которыми мы имеем де-
ло, включают эти шесть шагов, сумми-
Лает собака
*-----------
Возвращаемся
к чтению
Рис. 12.2.1
Идем к двери
с черного хода
Впускаем собаку
рованных выше.
Сценарий 2. Предположим теперь, что в то время, когда мы заняты
чтением, звонит телефон. Мы кладем книгу, отвечаем на звонок, кладем
трубку в конце разговора и возвращаемся к чтению.
Последовательность событий здесь фактически идентична сценарию 1. Раз-
личие заключается в источнике прерывания — телефон вместо собаки — и в
том, как мы обходимся с прерыванием, — телефонный разговор вместо от-
крывания двери с черного хода.
Сценарий 3. Предположим теперь, что мы читаем, когда слышим лай
собаки с черного хода. Мы кладем книгу, поднимаемся и начинаем движение
к черному ходу. Идя туда, мы слышим телефонный звонок, т. е. в процессе
обработки одного прерывания мы были прерваны второй раз. Можно исполь-
зовать следующий подход к этой, несколько более сложной ситуации. По-
скольку только секунда или две потребуется, чтобы достичь черного хода и
впустить собаку, мы продолжим обработку прерывания, вызванного лаем
собаки. Когда мы завершим эту задачу, у нас еще останется время, чтобы от-
ветить на звонок и справиться с этим прерыванием. Таким образом, мы в
данном случае решили обработать эти два прерывания последовательно, в
порядке их поступления. Аналогично, если бы мы ответили на телефонный
звонок и в середине разговора услышали лай собаки, то могли дать возмож-
ность псу лаять до тех пор, пока не положили бы трубку. Опять мы обраба-
тывали бы прерывания последовательно.
Из“за сложности, внесенной вторым прерыванием, кратко проанализиру-
ем эту ситуацию. Когда сначала лает пес, мы делаем мысленное замечание о
том, что делаем (читаем) , и начинаем обработку прерывания, вызванного ла-
ем собаки. То есть мы начинаем движение к черному ходу. Когда раздается
телефонный звонок, мы сознательно принимаем решение, что на некоторое
время будем игнорировать это прерывание, и, таким образом, продолжаем
идти к двери и впускаем собаку. Завершив обработку прерывания, вызван-
ного лаем собаки, мы теперь просматриваем наше мысленное замечание, что-
бы определить, как вернуться к тому, что мы делали, когда пес впервые прер-
вал наше занятие. Однако мы понимаем, что есть второе прерывание (выз-
ванное телефонным звонком), с которым надо справиться до того, как мы
сможем вернуться к чтению. Таким образом, мы отвечаем на телефонный
звонок, разговариваем с тем, кто нам позвонил,и кладем трубку. Теперь мы
опять просматриваем наше замечание и возвращаемся к чтению. Диаграмма,
поясняющая эту ситуацию, показана на рис. 12.2.2.
Чтение
Лает собака
Идем к двери
с черного хода
Возвращаемся
к чтению
Звонит телефон
(игнорируем)
11 Впускаем собаку
Готовимся вернуться к чтению
(однако прерывание со стороны
телефона все еще ждет своего
обслуживания)
_________________ .Отвечаем на
телефонный
вызов
__________________] ^Кладем трубку
Рис. 12.2.2
Сценарий 4. В этом сценарии, как и в сценарии 3, мы полагаем, что
заняты чтением, когда раздается лай собаки. Когда мы идем к черному ходу,
звонит телефон. Положим, однако, что мы ожидаем важного звонка. В та-
ком случае мы, вероятно, решим, что собака подождет какое-то время и от-
ветим на телефонный звонок. После завершения разговора мы продолжим
движение к черному ходу, впустим собаку и возобновим чтение.
Эта ситуация очень похожа на сценарий 3,но в главном отличии, т. е.в том,
что мы временно откладываем обработку прерывания, вызванного лаем со-
баки, заключена идея существенной важности, которая опять заслуживает
углубленного анализа.
Когда раздается лай собаки, мы кладем книгу и делаем мысленное замеча-
ние, которое будем называть замечанием А, о том, чем мы были заняты в мо-
мент возникновения прерывания. Таким образом, замечание А относится к
тому факту, что мы были заняты чтением. Затем мы начинаем обработку пре-
рывания, вызванного лаем собаки. Но, находясь на пути к черному ходу,
слышим телефонный звонок, и по причинам, которые были объяснены вы-
ше, решаем, что должны на него ответить. Как мы обрабатываем это преры-
вание? Таким же способом, как и первое, т. е. мы делаем замечание (замеча-
ние Б), о том, чем мы были заняты, когда произошло прерывание — направ-
лялись к черному ходу, чтобы впустить собаку, — и отвечаем на телефонный
звонок. Завершив разговор и положив трубку (т. е. обработав прерывание,
вызванное телефонным звонком) , мы опять задаемся вопросом о том, чем
же мы были заняты. Ответ заключается в нашем замечании. Но в каком?
Мы ’’записали” два замечания: А и Б; к какому же из них мы должны об-
ратиться? Ответ, конечно, состоит в том, что мы должны посмотреть послед-
нее из ’’записанных”, т. е. замечание Б. Сделав так, мы возвращаемся к на-
шей задаче — впустить собаку. Наконец, мы обращаемся к заметке А и во-
232
Чтение
Лает собака
*------------
Идем к двери
с черного хода
Возвращаем-
ся к чтению
^Звонит телефон
Возвращаем-
ся к собаке
1'Впускаем
собаку
Отвечаем на
телефонный
вызов
Кладем трубку
Рис. 12.2.3
зобновляем чтение. Пониманию последовательности происходящих событий
опять поможет диаграмма (рис. 12.2.3).
Мы отмечали выше, что, завершив обработку прерывания, вызванного
звонком телефона, мы должны обратиться к замечанию Б для определения
того, что должно быть возобновлено. Если вместо этого мы обратились бы
к замечанию А, то, следовательно, сразу же вернулись бы к чтению, а обра-
ботка прерывания, вызванного лаем собаки никогда бы не была завершена, и
пес оставался бы у черного хода бесконечно в ожидании, когда его впустят.
Таким образом, существенным является то, что мы мысленно ’’записываем”
эти замечания, чтобы запомнить, к чему должны вернуться после обработки
прерывания, и обращаемся к ним в обратном порядке. То есть последнее
’’записанное” замечание будет первым, к которому мы обратимся после за-
вершения обработки прерывания. Если читатель найдет, что последователь-
ность ’’последнее записанное замечание — первое считанное замечание” напо-
минает структуру стека ’’последним пришел — первым вышел”, то, значит,
он предвидит те концепции, которые будут обсуждаться далее в этой главе.
В каждом из двух последних примеров оба прерывания были в конце кон-
цов обработаны. В первом случае они рассматривались последовательно. Во
втором обработка прерываний была вложенной \ т. е. обработка первого
прерывания была инициирована, но затем приостановлена временно, пока об-
рабатывалось второе прерывание. Для людей такое вложение прерываний мо-
жет создать определенные проблемы, если вложения становятся довольно
глубокими. Следовательно, если обработка какого-то прерывания сама пре-
рывается и это случается несколько раз, то мы начинаем терять нить наших
мысленных замечаний, что влияет на правильность возврата к тому, чем мы
были заняты до прерываний, или мы начинаем обрабатывать эти замечания в
неправильном порядке, т. е. забываем, какое замечание было ’’записано”
последним. К счастью, вычислительные машины с такими проблемами не
сталкиваются, поскольку ничего не записывается ’’мысленно”, а определен-
ный тип стековой структуры всегда обеспечивает правильный ход вещей.
Предлагаем последний сценарий, который несколько отличается от пред-
шествующих тем, что включает в себя подавление прерывания.
Сценарий 5. Предположим теперь, что вместо развлекательного чте-
233
ния мы заняты подготовкой к выпускному экзамену. Эту задачу мы счита-
ем настолько важной, что решаем отключить звонок телефона. С телефо-
на, все же можно звонить куда-то, но если произойдет входящий вызов, то
звонок не зазвенит, и мы не узнаем о том, что нас кто-то вызывал. Предполо-
жим, что, когда мы готовимся к экзамену, лает пес и просит его впустить.
В определенном смысле это прерывание. Однако из-за важности той задачи,
которой мы заняты, мы просто игнорируем его, оставляя собаку под дверью.
Несмотря на просьбу собаки, мы продолжаем подготовку к экзамену. Ана-
логично, если кто-то попытается вызвать нас по телефону, мы также игнори-
руем это прерывание. Но заметьте, что эти два прерывания концептуально
различны. В случае с собакой мы понимаем, что пес требует какого-то обслу-
живания, и просто решаем его не делать. В случае же входящего телефонного
вызова мы даже не знаем, что существует запрос, требующий обслуживания.
В дополнение к концепциям прерываний и их обработки в наши обсужде-
ния просочилось еще одно понятие — понятие иерархии важности, или систе-
ма приоритетов. Например, в сценарии 3 мы не отдавали какого-либо пред-
почтения лаю собаки или телефонному звонку — мы обрабатывали эти пре-
рывания в последовательном порядке. В сценарии 4, даже занимаясь соба-
кой, мы отдали приоритет телефону, когда он начал звонить. Наконец, в
сценарии 5 мы назначили себе (точнее сказать, нашей задаче подготовки к
экзамену) приоритет выше, чем лаю собаки. (Вспоминаем, что в этом приме-
ре телефон находился в таком состоянии, что даже не мог генерировать пре-
рывания.) Но даже в этом последнем случае приоритет, назначенный нашей
подготовке к экзамену, вероятно, не является самым высоким из возмож-
ных; несмотря на важность работы по подготовке к экзамену, маловероят-
но, что мы решимся проигнорировать прерывание, вызванное возникнове-
нием пожара в доме.
Суммируя результаты этого раздела, мы видим, что действия каждого из
нас нередко подвергаются прерываниям; мы справляемся с этими прерыва-
ниями какими-то разумными способами и делаем это так, что можем про-
должить свою работу после обработки прерывания. В случае нескольких пре-
рываний мы иногда обрабатываем их последовательно, в других же случаях
в результате нашего назначения приоритетов прерываниям мы занимаемся
вложенной обработкой прерываний. В действительности нет ничего необыч-
ного, когда время от времени мы меняем приоритеты событий — как мы ви-
дели, телефонный звонок не всегда получал предпочтение перед необходи-
мостью впустить собаку. Наконец, мы видим, что многие из этих действий
происходят на уровне подсознания, что является результатом нашего много-
летнего опыта в таких вопросах. И только в этом отношении при обработке
прерываний мы отличаемся от вычислительных машин.
12.3. ПРОЦЕССОРНЫЕ ПРЕРЫВАНИЯ
Прерывания и их особенности, описанные в предыдущем разделе, в суще-
ственной степени аналогичны тем типам прерываний, с которыми сталкива-
ются вычислительные системы. Центральный процессор редко получает воз-
можность выполнять программы пользователей от начала до конца — его ра-
бота часто прерывается целым рядом внешних событий, чаще всего запроса-
ми от внешних устройств на какой-либо вид обслуживания. Некоторыми
234
простыми событиями, которые могут вызывать прерывания обработки, вы-
полняемой ЦП, являются нажатие клавиши на терминале, израсходование бу-
маги на АЦПУ, завершение записи блока памяти на диск, или, то, что считы-
ватель перфокарт встретил карту со специальной меткой конца файла. Во
всех этих случаях устройство достигает такого состояния, в котором требу-
ется некоторое обслуживание со стороны ЦП, и устройство запрашивает та-
кое обслуживание путем прерывания текущей деятельности ЦП. Чтобы вве-
сти некоторые понятия и проанализировать особенности, присущие обработ-
ке прерывания ЦП, рассмотрим очень простое устройство, называемое внеш-
ними часами.
Все ЭВМ содержат ’’часы”, а именно, некоторые устройства, способные
’’тикать” через определенный фиксированный интервал времени. Такой тай-
мер необходим по целому ряду причин. Например, когда на электронную схе-
му, составленную из нескольких вентилей, поступают входные сигналы, то
нельзя ожидать, что выходной сигнал схемы появится незамедлительно,
поскольку требуется некоторое время в течение которого электронные ком-
поненты реагируют на эти сигналы. Задержки здесь получаются небольшими;
обычно они измеряются миллиардными долями секунды. Но тем не менее
некоторая задержка происходит, и одно из назначений внутренних часов со-
стоит в стробировании этих задержек, чтобы гарантировать наличие действи-
тельных данных на выходе подобной схемы. Извлечение содержимого ячейки
памяти — это также относительно медленная операция, поскольку элементы,
составляющие физические слова, обычно реагируют на сигналы достаточно
вяло, и, конечно, чтобы их можно было читать или записывать, они должны
реагировать упорядоченно.
Но эти часы, облегчающие управление внутренним потоком информации
в вычислительной системе, не имфот отношения к времени реального мира —
нет способа использовать их для индикации того, что сейчас полночь или 3 ча-
са 12 минут дня. По этой причине многие вычислительные системы содержат
также внешние (по отношению к ЦП), часы, которые позволяют им следить
за реальным временем. Эти часы обычно используются для выполнения цело-
го ряда функций, таких как слежение за количеством времени, в течение ко-
торого конкретный пользователь использовал систему, для индикации на
распечатываемых листингах времени ассемблирования исходного файла, а в
случае ЭВМ с разделением времени — для того, чтобы ЦП знал, что данный
пользователь исчерпал свой временной отрезок, и что ЦП следует перейти к
обслуживанию следующего пользователя. Хотя имеется целый ряд различ-
ных типов часов, которые можно найти в системах ЭВМ PDP-11, самые про-
стейшие, которые мы и исследуем, — это сетевые часы.
Как читателю, без сомнения, известно, большинство электрических уст-
ройств работают на переменном токе, вырабатываемом электростанциями.
На одном зажиме источника переменного тока напряжение всегда равно О В,
тогда как на другом зажиме оно меняется от + 110 до — 110 В. В противо-
положность этому в случае источника постоянного напряжения, такого как
девятивольтовая батарея для транзисторного приемника, мы полагаем, что
на одном зажиме напряжение всегда равно 0 В, а на другом всегда + 9 В.
(Причины, почему желателен переменный ток как противоположность по-
стоянному току, мы рассматривать здесь не будем.) Скорость, с которой
235
ЦП
Флаги
прерываний
Рис. 12.3.1
происходит изменение напряжения от положительного значения к отрица-
тельному и опять к положительному, называется частотой источника тока,
которая измеряется числом циклов в секунду. В большинстве регионов Со-
единенных штатов и Канады переменный ток имеет частоту 60 циклов в се-
кунду, или, как общепринято говорить, 60 Гц. (Частота переменного тока во
многих странах Европы равна 50 Гц.) Вполне возможно построить простое
электронное устройство, которое будет обнаруживать эти колебания источ-
ника тока от положительного значения к отрицательному, и всякий раз, ко-
гда такое колебание происходит, прерывать ЦП. Исследуем это явление не-
сколько подробнее.
Можно считать, что каждое внешнее устройство составляется из двух уз-
лов: самого устройства (в нашем случае это сетевые часы; другими примера-
ми являются терминалы, АЦПУ и т. п.) и контроллера устройства, т. е. элект-
ронной схемы, которая фактически взаимодействует с ЦП или другими
внешними устройствами через шины адресов, данных и управления.
Рис. 12.3.1 — это некоторое расширение рис. 1.2.1, выявляющее несколько
новых особенностей. Во-первых, обратите внимание, что ЦП содержит флаги
прерываний — несколько внутренних битов в процессоре, роль которых
вскоре станет ясной. Сам контроллер содержит статусный регистр — 16-бито-
вое слово, отдельные биты которого дают информацию о состоянии устрой-
ства, что и следует из названия регистра. Внутреннее строение статусного ре-
гистра часов показано на рис. 12.3.2. Вообще говоря, статусный регистр ас-
социируется с каждым контроллером устройства, но многие устройства на-
столько сложны, что требуются дополнительные регистры. Сюда могут вхо-
дить регистр данных (16-битовое слово, через которое происходит обмен дан-
236
15 8 7 6 О
Контрольный бит----
Бит разрешения прерываний
Рис. 12.3.2
ними между устройством и ЦП или оперативной памятью) , регистр ошибок
(биты которого используются для определения типа ошибки, если она слу-
чилась) , регистр счетчика слов и т. п. Сетевые часы — это настолько простое
устройство, что для них требуется только статусный регистр, но даже в этом
регистре значение имеют только два бита из 16, Статусный регистр часов не
располагается в оперативной памяти, но тем не менее может быть адресован.
То есть он имеет адрес и, следовательно, на него можно воздействовать лю-
бой стандартной инструкцией ЭВМ PDP-11 (с подобной ситуацией мы уже
сталкивались по поводу PSW). Статусный регистр часов размещается по адре-
су 777546.
Вспоминаем теперь, что сетевые часы тикают с частотой 60 раз в секунду
и, таким образом, один ”тик” случается каждые 16?мс. Что происходит,
когда часы тикают? Частично ответ зависит от состояния бита 6 в статусном
регистре, бита разрешения прерываний. Если этот бит сброшен, то ничего не
происходит (это не совсем верно, но для наших теперешних целей, когда мы
интересуемся действием прерываний на ЦП, этот ответ вполне корректен).
Итак, предположим, что, когда происходит ”тик” часов, бит 6, бит разреше-
ния прерываний, установлен. В результате этого происходит целый ряд собы-
тий, мы рассмотрим их с некоторой степенью детализации.
Часы уведомляют контроллер часов, что произошел ”тик” — т. е., что ис-
тек временный интервал в 16 у мс. Контроллер отвечает на эту информацию
двояко. Во-первых, он устанавливает (т. е. делает равным 1) бит 7 в статус-
ном регистре — контрольный бит. И во-вторых, он извещает ЦП об этом со-
бытии, посылая сигнал прерывания по одной из линий шины управления. Что
происходит с этим сигналом, когда он Достигает ПП? Возможно, что ЦП на-
ходится в таком состоянии, что даже не сможет отреагировать на него. На-
пример, ЦП может находиться в середине процесса выполнения инструкции
и, без сомнения, если он не завершит свою операцию, то может произойти
какая-либо неприятность. В действительности процесс, с помощью которого
контроллер часов уведомляет ЦП, что произошел ”тик” часов, не настолько
прямой, как мы здесь пытаемся убедить читателя. Когда часы тикают и кон-
троллер посылает сигнал прерывания по шине управления, ЦП об этом собы-
тии не уведомляется немедленно. Вместо этого устанавливается в 1 флаг пре-
рываний, который можно рассматривать как однобитовый регистр в ЦП.
Настало время более внимательно рассмотреть специфику выполнения ЦП
последовательности инструкций. Мы говорили, что всякий раз, когда ЦП за-
вершает выполнение программной инструкции, он просматривает PC, чтобы
получить адрес следующей инструкции, извлекает ее из оперативной памяти,
выполняет и т. д. В действительности же последовательность событий выгля-
дит следующим образом. Завершив выполнение инструкции, ЦП сначала про-
веряет флаг прерываний. Если этот флаг сброшен, то процессор продолжает
237
CLKINT = 000100
CLKSTS = 177546
;АДРЕС ВЕКТОРА ПРЕРЫВАНИИ ЧАСОВ
;АДРЕС СТАТУСНОГО РЕГИСТРА ЧАСОВ
HOURS: .BLKB 1 ;ХРАНЕНИЕ ЧАСОВ,
MINUTS: -BLKB 1 : МИНУТ,
SECS: .BLKB I : СЕКУНД И
SPLITS: .BLKB 1 ! 1/60-Х ДОЛЕЙ СЕКУНДЫ
MOV «TICK,CLKINT ;УСТАНОВИТЬ АДРЕС ПРОГРАММЫ ; ОБРАБОТКИ ПРЕРЫВАНИИ ЧАСОВ
MOV «100,CLKSTS :РАЗРЕШИТЬ ПРЕРЫВАНИЯ ?(НАЧАЛО ПОЛЕЗНОЙ ОБРАБОТКИ)
TICK: INCB SPLITS ;УЧЕСТЬ ОЧЕРЕДНОЙ ТИК
СМРВ SPLITS,«60. JНАБРАЛИ СЕКУНДУ?
ВНЕ RETURN ;НЕТ — ВЫХОД
CLRB SPLITS ;да — СБРОСИТЬ тики
INCB SECS ; И ДОБАВИТЬ СЕКУНДУ
СМРВ SECS,«60. ЖАБРАЛИ МИНУТУ?
BNE RETURN :НЕТ — ВЫХОД
CLRB SECS ;СБРОСИТЬ СЕКУНДЫ
INCB MINUTS ; И ДОБАВИТЬ МИНУТУ
СМРВ MINUTS,«60. ;НАБРАЛИ ЧАС?
BNE RETURN ЖЕТ — ВЫХОД
CLRB MINUTS :ЛА — СБРОСИТЬ КИНУТЫ
INCB HOURS ; И ДОБАВИТЬ ЧАС
RETURN: RTI ;ВОЗОБНОВИТЬ ОБРАБОТКУ
Рис. 12.3.3
работать как обычно, — извлекает следующую инструкцию, на которую ука-
зывает PC, и т. п. Но если флаг прерываний установлен, ЦП выполняет пере-
ход к ловушке, т. е. он предпринимает действия, почти идентичные его реак-
ции на программную инструкцию TRAP. В частности, он проталкивает в стек
текущее c(PSW), а также текущее с (PC). Обратите внимание, что это поме-
щенное в стек с (PC) содержит адрес инструкции, которая выполнялась бы
следующей, если бы флаг прерываний не был установлен. Затем, как и при
инструкции типа TRAP, новые значения PC и PSW мы получаем из фиксиро-
ванных ячеек оперативной памяти. Этими фиксированными ячейками явля-
ются 000100 (PC) и 000102 (PSW).
Таким образом, когда тикают часы, контроллер часов устанавливает кон-
трольный бит в статусном регистре и посылает сигнал прерывания по шине
управления — короче, часы генерируют прерывание. Центральный процессор
отвечает на это, помещая в стек содержимое PSW и PC (в указанном поряд-
ке) и получая новые значения PC и PSW соответственно из ячеек 000100 и
000102. Выполнение продолжается, конечно, с того адреса, который находит-
ся в 000100, поскольку теперь с(РС) = с(ОООЮО). Здесь в ответ на прерыва-
ние от часов, вероятно, будет выполняться небольшая подпрограмма, называ-
емая подпрограммой обработки прерываний от часов или, короче, обработ-
чиком прерываний. Мы предлагаем совсем простую подпрограмму для вы-
полнения этой работы (рис. 12.3.3).
Предположительно мы располагаем четырьмя ячейками памяти для хра-
нения реального времени. Одна ячейка, называемая HOURS, содержит число
часов, прошедших с полуночи. Две другие,MINUTS и SECS, ведут счет мину-
там и секундам в пределах часа, указанного в HOURS. А в четвертой, SPLITS,
подсчитывается число долей секунды (т. е.’’тиков” часов) с момента записи
238
последней секунды. Таким образом, каждый раз, когда c(SPLITS) = 6010,
мы увеличиваем c(SECS) на 1 и сбрасываем c(SPLITS) в 0. Аналогично,
каждый раз, когда с (SECS) становится равным 601О, мы очищаем его и уве-
личиваем с(MINUTS) на 1. Так же мы обходимся с HOURS. Кроме того, мы
полагаем, что первоначально в этих четырех ячейках были установлены соот-
ветствующие значения времени. Читатель увидит, что справиться с програм-
мой обработки прерываний довольно легко.
Поскольку читателя не затруднит проследить логику обработчика преры-
ваний в ячейке TICK, мы сосредоточимся на некоторых других особенностях
этого программного сегмента. Во-первых, мы дали символьное имя CLKINT
ячейке памяти 000100, первому слову вектора прерываний (аналогия с тер-
мином TRAP-вектор), т. е. первому из двух слов, из которых ЦП получит
новые значения содержимого PC и PSW в случае прерывания от часов. Анало-
гично, мы присвоили.имя CLKSTS адресу статусного регистра часов. Следу-
ющие четыре байта, показанные в листинге, это просто ячейки, где хранится
время. Инструкция MOV #TICK, CLKINT устанавливает адрес TICK в первом
слове вектора прерываний (обратите внимание, что со вторым словом этого
вектора мы ничего не делаем; об этом мы поговорим позднее). Следующая
инструкция, MOV #100, CLKSTS, разрешает прерывания от часов, т. е. уста-
навливает бит 6 в статусном регистре. В этом месте предположительно начи-
нается какая-то полезная обработка. Спустя одну шестидесятую секунды ча-
сы тикают и генерируется прерывание. Когда в процессе выполнения про-
граммы ЦП завершит выполнение текущей инструкции, он заметит, что флаг
прерываний установлен, и выполнит переход к ловушке. Текущее содержи-
мое PSW и PC будет помещено в стек, а новые значения PC и PSW будут получе-
ны соответственно из ячеек CLKINT и CLKINT+2. Поскольку с (CLKINT) =
= TICK, обработка теперь возобновится в подпрограмме обработки преры-
ваний. Здесь время, в виде числа ’’тиков” часов, будет скорректировано и в
конце концов будет выполнена инструкция RTI. Как мы знаем, действие
RTI состоит в выталкивании верхнего элемента стека дважды, соответствен-
но в PC и PSW. Выталкивание в PC приведет к возобновлению выполнения
с того места, где работа ЦП была прервана. Таким образом, полезная обра-
ботка вновь будет продолжена до следующего прерывания от часов, когда
описанный выше процесс повторится.
Есть целый ряд вопросов, сопряженных с этим процессом, несколькими
из которых мы будем заниматься некоторое время. Мы дадим ответы на них
в этой главе; фактически двумя из них мы займемся немедленно. Читателя
может как-то беспокоить тот факт, что часы прерывают полезную обработку
каждые 16 у мс, причем каждый раз должна выполняться подпрограмма об-
работки прерывания. Может показаться, что большую часть времени ЦП бу-
дет обрабатывать прерывания от часов, а не выполнять полезную обработку.
Эта проблема представляется еще более усложненной из-за того, что показан-
ная в листинге подпрограмма обработки прерываний не очень эффективна.
Те же функции можно было выполнить меньшим числом инструкций и без
многочисленных извлечений из памяти, потребляющих достаточно много, вре-
мени. (Обработчик прерываний был написан для обеспечения ясности и лег-
кости понимания, а не эффективности.) Но реально, даже в наихудшем слу-
чае, когда приходится каждый раз очищать байты памяти и затем увеличи-
239
вать их, полное время, затраченное обработчиком прерываний, существенно
меньше, чем 50 мкс. Таким образом, мы видим, что время, затрачиваемое на
обработку прерываний, действительно совершенно несущественно по сравне-
нию со временем, затрачиваемым на выполнение программы.
Другой вопрос касается PSW. В предыдущей главе мы говорили о том, что
при выполнении инструкции TRAP сохранение c(PSW) представляется не-
нужным и что даже трудно придумать пример, в котором было бы необходи-
мо сохранять c(PSW) при выполнении инструкции TRAP. И, поскольку то,
что происходит во время прерывания, почти идентично тому, что происходит
при выполнении инструкции TRAP, читатель может подумать, что то же самое
справедливо и для обработки прерывания центральным процессором, т. е.
что сохранения и восстановления содержимого PSW не требуется. Но нам сле-
дует знать, что между обработкой инструкции TRAP и обработкой прерыва-
ния есть два существенных различия. Первое — это то, что TRAP — програм-
мная инструкция, тогда как прерывание — нет. И второе, невозможно преду-
гадать точное место при выполнении программы, в котором произойдет пре-
рывание; в некотором смысле прерывания — даже прерывания от часов —
это случайные события. Именно этот факт делает сохранение содержимого
PSW существенным для правильного выполнения программ. Чтобы понять
это, рассмотрим следующий пример.
Предположим, что некоторая программа содержит следующую пару ин-
струкций:
DEC R4
BNE LOOP
которые, очевидно, предназначены для управления каким-то циклом в про-
грамме. Рассмотрим ситуацию, когда непосредственно перед выполнением
c(R4) = 1. Тогда DEC R4 уменьшит c(R4) до 0, в результате чего бит Z в
PSW будет установлен в 1. Из-за этого ветвление по инструкции BNE LOOP
не будет выполняться, и процессор просто извлечет для обработки следую-
щую инструкцию. Но теперь предположим, что во время выполнения ин-
струкции DEC R4 происходит прерывание от часов:
DEC R4
* * здесь происходит прерывание от часов
BNE LOOP
Опять, поскольку c(R4) было уменьшено до 0, бит Z в PSW был установлен,
но теперь из-за прерывания ЦП должен выполнить переход к ловушке. Пред-
положим, однако, что перед передачей управления обработчику прерываний
содержимое PSW не сохраняется. Просмотр предложенной нами подпрограм-
мы обработки прерываний от часов выявляет, что независимо от того, какие
в ней выполняются инструкции, результатом последней инструкции всегда
будет ненулевое число, и, следовательно, после возврата из этой подпрограм-
мы и возобновления обработки в основной программе бит Z в PSW окажется
сброшенным. Возврат, конечно, произойдет к инструкции BNE LOOP, и про-
изойдет ветвление, которого в этом случае быть не должно. Таким образом,
мы видим, что, если в момент прерывания c(PSW) не сохраняется и позднее
не восстанавливается, то остается мало надежд на то, что удастся правильно
управлять работой программы.
240
12.4. ЕЩЕ РАЗ ОБ АДРЕСАЦИИ ПАМЯТИ
Внимательный читатель, возможно, заметил очевидное несоответствие в
предыдущем разделе. Мы утверждали (правильно), что адрес статусного ре-
гистра часов есть 777546, и все же в листинге программы в этом разделе мы
видим, что символ CLKSTS, которым мы обозначили адрес этого статусного
регистра, получил значение 177546, а не 777546. Но обратите внимание, что
оператор прямого присваивания
CLKSTS = 777546
был бы бессмыслицей, поскольку для числа 777546 требуется 18-битовое
представление, которое не может поместиться в 16-битовое слово оператив-
ной памяти. Поэтому мы сделали лучшее из того, что было возможно, и при-
своили CLKSTS значение 177546, в котором два самых старших бита просто
отброшены. Но как в этом случае оператор MOV #100, CLKSTS, который ас-
семблируется как
012767
000100
177456
может обращаться к статусному регистру часов?
Ответ на этот вопрос заключается в самой аппаратуре и в том, как она об-
ращается с адресами. В действительности адресация в ЭВМ PDP-11 является
18-битовой, а не 16-битовой1. Таким образом, каждое адресуемое слово —
слова оперативной памяти, регистры устройств, PSW — имеет 18-битовый ад-
рес, хотя обращения из программ по таким адресам всегда выражаются 16-
битовыми. словами. Следовательно, очевидно, что аппаратура должна пре-
образовывать такой 16-битовый адрес в 18-битовый. Она делает это следую-
щим образом. Для построения 18-битового адреса из 16-битового слова би-
ты 15, 14 и 13 этого адреса собираются на логической схеме И. Полученный
результат (0 или 1) используется в качестве битов 17 и 16 18-битового ад-
реса (рис. 12.4.1) .Такимобразом, адрес статусного регистра 177546преобра-
зуется аппаратурой в конфигурацию, которая показана на рис. 12.4.2. Но та-
кой адрес, как 142307, опять преобразуется в 142307,что видно из рис. 12.4.3.
В этой аппаратной схеме предполагается, что адресация памяти в ЭВМ PDP-11
выглядит следующим образом:
000000
157777
760000
777777
1 На некоторых моделях PDP-11 используется 22-битовая адресация, и там генерация
полных адресов из 16-битовых адресов выполняется не так, как описывается в этом раз-
деле. Однако эти различия никак не отражаются на программировании для внешних
устройств.
241
18-битовый адрес
16-битовый адрес
777546 =1 1 1 111111101 100110
Рис. 12.4.2
142307 = 0 0 1 1 0 0 0 1 00110 0 0111
Рис. 12.4.3
и что адреса между 160000 и 757777 включительно никогда не могут быть
сгенерированы. В действительности некоторые модели ЭВМ PDP-11 содержат
(необязательную) аппаратуру, называемую диспетчером памяти, которая
преобразует адреса несколько более сложным способом, так что в этот про-
межуток может быть помещена физическая память.
В большинстве систем PDP-11 адреса между 000000 и 157777 являются ад-
ресами байтов оперативной памяти, тогда как адреса от 760000 до 777777
представляют, например, различные регистры устройств, которые не распола-
гаются в оперативной памяти.
12.5. РЕЖИМ РАБОТЫ БЕЗ ПРЕРЫВАНИЙ
В разд. 12.3 мы видели, что если часы требовали обслуживания - тикали -
в то время, как ЦП выполнял, например, программу пользователя, то в ЦП
по шине управления передавался сигнал прерывания, управление процессо-
ром передавалось подпрограмме обработки прерываний от часов, реальное
время подвергалось коррекции и затем, поскольку была сохранена достаточ-
ная информация (а именно, содержимое PC и PSW), возможно было возоб-
новить выполнение программы пользователя. Это, конечно, полезная схема
для ведения времени дня, но она не ограничена использованием лишь сете-
вых часов. Любое устройство (клавиатура, АЦПУ, считыватель перфокарт),
когда оно нуждается в обслуживании, может уведомить об этом ЦП, генери-
242
руя сигнал прерывания. Есть нечто загадочное в том, что в статусном регист-
ре часов, как и в статусных регистрах большинства других устройств, имеет-
ся бит, называемый битом разрешения прерываний. Мы видели, что если этот
бит установлен, то каждый ”тик” часов вызывает прерывание. Из этого мы,
однако, заключаем, что если бит разрешения прерываний сброшен, то, когда
устройство требует обслуживания — тикают часы или< происходит что-либо
другое, — такой сигнал прерывания не генерируется. И действительно, это со-
вершенно верно. Но как в этом случае ЦП может узнать,что устройству тре-
буется обслуживание? Ответ состоит в том, что он не может этого узнать.
Но ситуация не настолько безнадежна, как это может поначалу показать-
ся. Чтобы как-то разобраться в этом вопросе, давайте вернемся на время к
разд. 12.2. Вспоминаем, что там мы разбирали различные способы, какими
мы обрабатываем прерывания в нашей повседневной жизни, а в сценарии 5
мы отключили звонок телефона. В результате мы лишили это устройство
возможности генерировать прерывания, которые должны были привлечь на-
ше внимание к тому, что требуется обслуживание, когда раздается звонок.
Важно отметить, что само устройство мы не отключили — телефон так же ос-
тавался способным выполнять все свои функции за исключением генерации
сигнала прерывания. Таким образом, оставалась возможность, чтобы теле-
фон принимал входящий вызов, но поскольку мы ничего не знали о суще-
ствовании этого вызова, то и не обрабатывали его.
Означает ли это, что мы не можем обнаружить входящий вызов? Ни в
коей мере. Даже когда звонок отключен, мы можем обслуживать входящие
вызовы, если согласимся, что входящий вызов остается на линии в течение
10 с до тех пор, пока вызывающий абонент не положит трубку. Мы подни-
маем трубку принимающего телефона. Если слышим гудок, то кладем труб-
ку, ожидаем 10 с и опять поднимаем трубку. Если слышим гудок, кладем
трубку и повторяем этот процесс. Если поступает вызов, то рано или поздно
мы поднимем телефонную трубку и обнаружим отсутствие гудка. Теперь
мы готовы переговорить с вызывающим абонентом. Хотя представляется ма-
ловероятным, чтобы мы действительно выполняли подобный процесс, мы
очертили алгоритм, чтобы показать, как работает’’программа для обнаруже-
ния входящего вызова”:
1. Ожидать 10 с.
2. Поднять трубку принимающего телефона.
3. Если слышим гудок, перейти к шагу 1.
4. Если гудок не обнаружен, сказать ’’алло” и начать разговор.
Конечно, мы никогда не пытаемся поступать с входящими вызовами по-
добным образом, поскольку при этом мы отдавали бы все наше время этой
простой задаче, и поэтому оставалось бы мало надежд, что нам удастся сде-
лать что-либо другое и полезное в то время, пока мы ожидаем входящего
вызова. Ответы на телефонные звонки лучше всего обрабатываются на осно-
ве прерываний, и именно по этой причине телефоны оснащаются звонками
(вспомним также о дверных звонках или о свистке футбольного судьи).
Но мы хотим здесь подчеркнуть тот момент, что несмотря на очевидную аб-
сурдность таких действий, можно работать с этим устройством (телефоном)
без использования прерываний.
243
Вычислительная система может работать с одним или несколькими своими
устройствами без использования прерываний с помощью процесса, который
в заметной степени аналогичен процедурам в вышеприведенном примере.
Чтобы понять, как она может это делать, вернемся к тикающим часам, но на
этот раз будем полагать, что бит разрешения прерываний (бит 6) в статусном
регистре часов сброшен. Как ЦП сможет определить, что произошел ”тик”
часов? Чтобы ответить на этот вопрос, познакомимся более подробно с так на-
зываемым контрольным битом (битом 7) в статусном регистре. Вспоминаем
из разд. 12.3.3, что всякий раз, когда часы тикают через 1/60 с, они извещают
об этом свой контроллер, и последний предпринимает два действия: он уста-
навливает контрольный бит и передает сигнал прерывания в ЦП. Однако, как
видно из программного сегмента на рис. 12.3.3, обработчик прерываний с
контрольным битом ничего не делает. Но в действительности в этом примере
с контрольным битом происходит следующее: инструкция MOV #100,
CLKSTS устанавливает бит разрешения прерываний, т. е. выполняет факти-
чески интересующее нас действие и сбрасывает контрольный бит, на который
мы не обращаем никакого внимания. Одну шестидесятую секунды спустя
часы тикают, и контроллер устанавливает контрольный бит, а также генери-
рует прерывание. Поскольку в обработчике прерываний от часов нет ника-
кой программной инструкции для сброса (очистки) этого бита, то он просто
остается установленным. Одну шестидесятую секунды спустя часы тикают
вновь. Контроллер еще раз устанавливает контрольный бит, вернее, посколь-
ку он уже был установлен, то просто таким остается, т. е. с ним вовсе ничего
не происходит. Этот контрольный бит, которым мы пренебрегли, будет иг-
рать ключевую роль при работе с часами без прерываний.
Даже если прерывания от часов запрещены, выполняющаяся программа
может вести подсчет числа ’’тиков” часов следующим образом. Программа
проверяет состояние статусного регистра часов. Если контрольный бит сбро-
шен, то она немедленно возвращается опять к проверке состояния контроль-
ного бита. Если она находит, что он все еще сброшен, то повторяет этот про-
цесс до тех пор, пока не определит, что контрольный бит стал установлен-
ным; в этом месте программа может, например, увеличить счетчик’’тиков”,
сбросить контрольный бит и вернуться назад к циклу проверки состояния
этого бита. Таким образом, программа может подсчитывать ’’тики” часов и,
поскольку часы тикают (но без прерываний) 60 раз в секунду, может отсле-
живать прошедшее время. Такой процесс постоянной или частой проверки
состояния некоторого устройства для определения (без использования пре-
рываний) того, нуждается ли оно в обслуживании, называется опросом уст-
ройства.
Но обратите внимание, что в результате этой процедуры ЦП привязывает-
ся к единственной задаче подсчета числа ’’тиков” часов (в это время он дей-
ствительно не может делать ничего другого), и читатель может удивиться, по-
чему мы вообще захотели поступать именно так. Один ответ состоит в том,
что по разным причинам нам может потребоваться, чтобы выполнение про-
граммы (вероятно, более точно, ее обработка ЦП) задержалось на некоторый
фиксированный отрезок времени — на некоторый временной интервал. Ко-
нечно, подсчет ’’тиков” часов — это все-таки тоже обработка, но естественно
считать, что в течение этого времени ЭВМ находится в некотором состоянии"
244
CLKSTS - 177546 / ;СТАТУСНЫЙ РЕГИСТР ЧАСОВ
MOV J900..R2 ;СОСЧИТАТЬ 900 ’ТИКОВ’ ; < = 15 секунд»
CHECKS TSTB CLKSTS BPL CHECK CLRB CLKSTS ВЕС R2 BNE CHECK .ПРОВЕРИТЬ СТАТУСНЫЙ РЕГИСТР ;БИТ 7=0— ПРОВЕРЯТЬ СНОВА ; БИТ 7 = 1 — СБРОСИТЬ ЕГО ; И УМЕНЬШИТЬ СЧЕТЧИК НА 1 ‘.ЕСТЬ ЕНЕ ТИКИ — ПРОВЕРЯТЬ СНОВА. ; ИНАЧЕ — СДЕЛАНО И НАДО ПРОДОЛДАТЬ
Рис. 12.5.1
застоя. Например, в программе, которая генерирует графическое изображе-
ние на телевизионном экране, мы можем захотеть отобразить картинку, сде-
лать задержку, вероятно на полсекунды, сгенерировать другую картинку,
опять сделать задержку и т. д., чтобы создать впечатление подвижного изо-
бражения на экране. Мы полагаем, что после того, как отображена первая
картинка, до отображения следующей картинки никакая полезная обработ-
ка не нужна, так что задержка, необходимая для получения желаемого эф-
фекта, действительно ”не стоит” нам никакого процессорного времени.
В качестве другого примера рассмотрим пользовательскую программу,
которой в процессе выполнения требуется какая-то информация от опера-
тора ЭВМ. В этом месте она может послать сообщение на терминал операто-
ра и ждать ответа, без которого она не может продолжать свою работу. Нет
ничего необычного, когда подобные программы переходят к ожиданию вво-
да с клавиатуры, т. е. ждут ответа оператора в течение фиксированного вре-
менного интервала, например 15 или 30 с. Если в течение этого времени ника-
кого ответа не поступает, программа может побудить оператора к действию
другим сообщением или может просто заключить, что при отсутствии откли-
ка надо использовать ответ, установленный по умолчанию.
На рис. 125.1 показан программный сегмент, который ставит временную
метку через 15 с путем опроса часов и подсчета 900 ’’тиков”. Мы полагаем,
что прерывания от часов запрещены. Учитывая предыдущее обсуждение, чи-
татель не должен испытывать каких-то трудностей при анализе этих несколь-
ких программных инструкций.
Таким образом, мы видим, что сетевые часы могут использоваться в режи-
мах с прерываниями и без прерываний. В первом случае они легко могут
применяться в качестве истинных часов для ведения времени дня. Во втором
случае время дня разумно вести не удается, и фактически часы здесь исполь-
зуются как интервальный таймер, во многом подобный, например, таймеру
стиральной машины, заранее устанавливаемому на фиксированное число ми-
нут. Но часы в этом отношении не являются чем-то особенным — почти с лю-
бым внешним устройством можно работать как с прерываниями, так и без
прерываний, и мы увидим еще один пример этого. Однако, имея дело с не-
прерывающим устройством, мы должны знать, что, по-видимому, заставим
ЦП заниматься исключительно задачей управления этим устройством, причем
так же и по тем же причинам, по каким требуется безраздельное внимание со
стороны процессора непрерывающим часам.
12.6. ДАЛЬНЕЙШИЕ ПОДРОБНОСТИ ПОСЛЕДОВАТЕЛЬНОСТИ ПРЕРЫВАНИЯ
Проведенное нами обсуждение показывает, что последовательность собы-
тий, происходящих, когда какое-то устройство генерирует прерывание с за-
245
ЦП
Флаги
прерываний
Рис. 12.6.1
просом на обслуживание, довольно проста. Устройство сигнализирует ЦП,
что ему требуется обслуживание, и когда ЦП способен его выполнить, он по-
мещает в стек c(PSW) и с (PC) и переходит к подпрограмме обслуживания
этого устройства (к обработчику прерываний). Когда обслуживание завер-
шается, инструкция RTI восстанавливает состояние ЦП, а выполнение про-
граммы возобновляется с того места, где оно было приостановлено. Но, как
покажет следующий пример, реальная картина намного сложнее того, что мы
здесь изобразили.
Предположим, что вычислительная система поддерживает четыре внеш-
них устройства: А, В, С и D (рис. 12.6.1). Предположим далее, что все эти
устройства способны генерировать прерывания и что устройства В и D дей-
ствительно генерируют прерывания фактически одновременно. Такая ситуа-
ция ни в коей мере не является невозможной или невероятной. Мы знаем,
что для выполнения какой-либо инструкции ЦП требуется некоторый корот-
кий интервал времени, и совершенно ясно, что в пределах этого интервала
возможно нажатие клавиши на терминале и, например, ”тик” часов. Оба эти
события приведут к установлению флага прерываний, и в этой ситуации
возникают следующие вопросы:
1. Как ЦП узнает, какое устройство (или в нашем случае устройства) выз-
вало прерывание?
2. Как ЦП может определить адрес вектора прерываний, который должен
использоваться для перехода к ловушке?
3. Если, например, обслуживается устройство D, то будет ли прерываний
от устройства В хотя бы подтверждено? Если так, то когда? В одно и то
же время оба устройства обслуживать невозможно.
4. Если однажды флаг прерываний установлен, то когда и как он очища-
ется?
В этом разделе мы ответим на эти вопросы, но поставим другие, с которы-
ми будем иметь дело в последующих разделах. Чтобы понять, как разрешает-
246
ся этот очевидный конфликт прерывающих устройств, мы должны более де-
тально рассмотреть сам процесс прерывания и, в частности, различные сигна-
лы, проходящие по шине управления между ЦП и устройством. В последую-
щем обсуждении мы не будем анализировать все детали последовательности
прерывания; некоторые из них не имеют отношения к разрешению той про-
блемы, которой мы занимаемся, и без нужды запутали бы и без того уже
сложную ситуацию. Отметим также, что все, что мы здесь говорим, примени-
мо к ЭВМ PDP-11; другие машины обрабатывают прерывания несколько по-
иному. Тем не менее различные сигналы запросов, предоставлений и под-
тверждений, предохранение состояния ЦП, установка и сброс флагов преры-
ваний и т. п. — это все задачи, которыми так или иначе большинство ЭВМ
должно заниматься. Ясное понимание того, как ГОР-11 обрабатывает преры-
вания, послужит читателю хорошей отправной точкой для понимания концеп-
ций и изучения обработки прерываний на ЭВМ с другой архитектурой.
Когда какое-либо устройство способно сгенерировать прерывание для за-
проса обслуживания, происходят следующие события:
1. Контроллер устройства устанавливает единицу на линии шины управле-
ния, называемой линией запроса шины — BR (Bus Request). Мы гово-
рим, что контроллер выставляет сигнал BR. Результатом этого является
установление флага прерываний в ЦП.
2. Когда ЦП в состоянии отреагировать на это, т. е. когда он завершает
выполнение своей текущей инструкции, он выставляет сигнал, называ-
емый сигналом предоставления шины — BG (Bus Grant), на шине управ-
ления.
3. Каждое устройство на шине управления отвечает на BG следующим об-
разом. Если устройство не выставляло сигнал BR, т. е. если оно не тре-
бовало обслуживания от ЦП, то оно просто передает сигнал BG к следу-
ющему устройству на шине. Но если устройство затребовало обслужива-
ние, то оно предпринимает три следующих действия:
а) блокирует сигнал BG от дальнейшего прохождения по шине;
б) посылает сигнал подтверждения в сторону ЦП, чем уведомляет ЦП о
том, что его сигнал BG был выявлен каким-то устройством;
в) очищает сигнал BR.
4. Центральный процессор получает сигнал подтверждения от устройства
и отвечает на него очисткой сигнала BG.
5. Контроллер устройства помещает адрес первого слова вектора преры-
ваний этого устройства на шине данных и посылает еще один сигнал по
шине управления в сторону ЦП. В литературе по ЭВМ PDP-11 этот по-
следний сигнал называют запросом прерывания — INTR (INTerrupt
Request).
6. Центральный процессор отвечает на INTR тем, что использует адрес на
шине данных в качестве адреса вектора ловушки (прерывания) и вы-
полняет стандартную последовательность перехода к ловушке.
Все это может показаться слишком сложным для передачи управления
подпрограмме обработки прерываний, но мы увидим, что в действительности
это минимальная схема, позволяющая правильно обрабатывать прерывания,
особенно тогда, когда в одно и то же время два или несколько прерываний
247
ожидают решения, как мы сейчас и предполагаем. Давайте вернемся к четы-
рем сформулированным ранее вопросам и посмотрим, как можно ответить
на большинство из них.
Первый вопрос состоял в том, как ЦП узнает, какое устройство прерыва-
ет его работу. Мы видим, что фактически он этого не знает, однако ему и не
надо этого знать. В некотором смысле устройство однозначно определяется
адресом своего вектора прерывании. Этот адрес ’’зашивается” в аппаратуре
контроллера и, следовательно, для каждого устройства фиксирован. В соот-
ветствующий момент времени контроллер выставляет этот адрес на шине
данных. Таким образом, дан ответ и на второй вопрос. Ответ на третий во-
прос мы на некоторое время отложим и заметим, что ответ на четвертый во-
прос о том, когда и как очищается флаг прерываний, уже получен: сам кон-
троллер сбрасывает BR после того, как ЦП признает устройство, послав свой
сигнал BG.
Чтобы ответить на третий вопрос, рассмотрим детально конкретную ситу-
ацию. Вспомним наше предположение о том, что устройства В и D сгенериро-
вали прерывания одновременно. Мы полагаем также, что эти устройства рас-
положены на шине в той конфигурации, которая показана на рис. 12.6.1.
Когда ЦП обнаруживает, что установлен флаг прерываний, он передает сиг-
нал BG по шине управления. Первым этот сигнал видит контроллер устрой-
ства А, поскольку оно расположено ближе всего к ЦП. Но это устройство не
запрашивало обслуживание и поэтому просто передает сигнал BG дальше по
шине управления. Следующее устройство, которое обнаруживает этот сигнал,
это устройство В, и поскольку оно затребовало обслуживание, блокирует
дальнейшее распространение сигнала и помимо выполнения других действий
сбрасывает сигнал BR, который был первоначально выставлен им и устрой-
ством D. Действительно ли очищается сигнал BR? Да, но лишь на какой-то
момент, поскольку устройство D все еще выставляет этот сигнал, так как
оно не получало сигнала BG от процессора. Таким образом, хотя флаг преры-
ваний, установленный сигналом BR, сбрасывается, он снова устанавливается.
Следовательно, хотя устройство В обслужено процессором и произошел пере-
ход к ловушке через вектор прерываний устройства В, флаг прерываний все
еще установлен.
12.7. ПРИОРИТЕТЫ УСТРОЙСТВ
Продолжая пример из предыдущего раздела, мы видим, что устройство В
получило контроль над ЦП и что прерывание от устройства D все еще ожида-
ет обработки. Более точно, поскольку устройство В ближе (физически) на
шине к ЦП, чем устройство D, ЦП отвечает устройству В и загружает PC и
PSW содержимым вектора прерываний устройства В. Поэтому, когда выпол-
нение готово возобновиться, PC содержит адрес первой инструкции подпро-
граммы обслуживания прерываний устройства В (вспоминаем, что первона-
чальные значения PSW и PC были помещены в стек). Но ЦП не извлекает и не
выполняет эту первую инструкцию незамедлительно. Прежде чем это сделать,
ЦП просматривает флаг прерываний, т. е. совершает действие, которое он
всегда предпринимает перед извлечением следующей инструкции, и обнару-
живает, что этот флаг установлен, так как сигнал BR был выставлен устрой-
ство^ D. Как и прежде, ЦП отвечает посылкой сигнала предоставления шины.
248
Основная
обработка
ЦП обнаруживает,
что установлен
флаг прерываний
(подтверждает
Обработчик
устройства В
прерывание от В)
PSW и PC
* Ж —^-помещаются в стек—»
(Подтверждение
прерывания от D)
Обработчик
устройства D
PSW и PC
* —► помещаются в стек-*
Возобновление
основной обработки
Возобновление под-
программы обработки
устройства В
RTI восстанавливает
содержимое
"PC и PSW
РИС. 12.7.1
Опять устройство А не требует обслуживания и поэтому пересылает этот сиг-
нал дальше по шине к устройству В. Но устройство В также не требует обслу-
живания — вспоминаем, что оно сбросило свой сигнал запроса шины. Поэто-
му устройство В передает сигнал к устройству С, которое в свою очередь пе-
редает его к устройству D. Поскольку устройство D действительно нуждает-
ся в обслуживании, оно подтверждает сигнал BG, помещает адрес вектора
прерывания на шине данных, и ЦП выполняет другой переход к ловушке.
В результате этого c(PSW) и с (PC) помещаются в стек. Обратите внимание,
что эти значения PSW и PC соответствуют первой инструкции (так еще и не
выполненной) обработчика прерываний устройства В. Затем ЦП извлекает
содержимое PC и PSW, соответствующее подпрограмме обработки прерыва-
ний устройства D, и с этого начинает выполнение. Таким образом, хотя под-
программа обработки прерываний устройства В получила управление, это
управление было недолговечным. Фактически, прежде чем могла быть вы-
полнена первая инструкция, управление было передано подпрограмме уст-
ройства D.
Если предполагать, что других прерываний нет, то подпрограмма обслужи-
вания устройства D будет работать до завершения и выполнит инструкцию
RTI, которая вытолкнет новые значения PC и PSW из стека. Поскольку эти
значения PC и PSW соответствуют началу подпрограммы устройства В, нач-
нется выполнение подпрограммы обработки прерываний устройства В, кото-
рое продлится до завершения, и с помощью другой инструкции RTI возоб-
новится то, что выполнялось в момент возникновения этцх двух прерыва-
ний. Диаграмма, поясняющая эту ситуацию, показана на рис. 12.7.1. Таким
образом, хотя устройство В расположено ближе к ЦП, чем устройство D, и
было обслужено ЦП первым, тем не менее первой завершила выполнение
подпрограмма обслуживания устройства D.
Вспомним из наших примеров в разд. 12.2, что, имея дело с повседневными
прерываниями, мы нередко налагаем (вероятно, подсознательно) на эти пре-
рывания некоторую систему приоритетов, или иерархию важности. Если два
249
прерывания случаются одновременно, мы можем решить, в каком порядке
их обрабатывать. Или, когда во время обработки какого-то прерывания, про-
исходит другое прерывание, мы можем временно приостановить наши дей-
ствия для того, чтобы справиться со вторым прерыванием. Аналогично и
внешним устройствам ЭВМ PDP-11 назначаются приоритеты, которые отража-
ют тот факт, что в действительности некоторые устройства более важны, чем.
другие. Например, часам, ведущим реальное время, вероятнее всего будет
назначен весьма высокий приоритет. Ибо, когда часы тикают, мы хотим быть
уверены, что этот ”тик” записывается достаточно быстро. Если жечдсы име-
ют низкий приоритет, то возможно, что когда произойдет ”тик”, мы окажем-
ся связаны прерыванием более высокого приоритета, так что пройдет доста-
точно времени, чтобы произошел второй тик (и, следовательно, второе пре-
рывание) до того, как мы сможем заняться его обработкой. Но определить
каким-то образом, что было два ’’тика”, нет никакой возможности; мы уве-
личим счетчик на 7, потеряв тем самым один ”тик” часов. По общему призна-
нию из-за потери 1/60 с не стоит огорчаться, есаи только это случается не ча-
сто. Но можно предполагать, что при низком приоритете это будет происхо-
дить нередко.
Уровень приоритета ЭВМ PDP-11 — это целое число от 0 до 7 включитель-
но. Чем больше число, тем больше уровень приоритета в том смысле, кото-
рый вскоре станет ясным. Внешним устройствам обычно назначаются приори-
теты с уровнями между 4 и 6 включительно. Уровень приоритета 7 представ-
ляет некий специальный случай и зарезервирован для цели, которой нет нуж-
да касаться в нашем теперешнем обсуждении. Приоритеты с уровнями от 0
до 3 для внешних устройств обычно не используются, но это не значит, что
они не используются вообще, в чем мы и убедимся в следующем разделе.
Вообще говоря, если два устройства требуют обслуживания от ЦП, то свое
внимание ЦП уделит прежде устройству с более высоким приоритетом. Что-
бы понять детали работы приоритетной схемы, необходимо опять заняться
аппаратурой.
В последнем разделе мы утверждали, что, когда устройство требует пре-
рывания работы ЦП, оно выставляет сигнал на линии запроса шины BR и что
ЦП отвечает на это, посылая сигнал предоставления шины BG. В действитель-
ности существуют четыре линии: от BR4 до BR7 и от BG4 до BG7. Каждое
устройство на шине'управления подключается, к одной такой линии запроса
шины и к соответствующей линии предоставления шины. Например, сетевые
часы подсоединяются к шине управления через линии BR6 и BG6. Поэтому,
когда часы запрашивают прерывание, оно происходит на уровне приоритета
6, контроллер часов выставляет сигнал на линии BR6 и получает сигнал под-
тверждения от ЦП по линии BG6. Из этой схемы вытекает несколько след-
ствий. Во-первых, каждое устройство, очевидно, имеет аппаратно-фиксиро-
ванный уровень приоритета, который определяется в момент подключения
устройства к шине. Вообще говоря, единственный способ изменить приори-
тет устройства — это физически перезаписать его. Во-вторых, нет единствен-
ного флага прерываний, а есть по одному для каждого уровня приоритета от
4 до 7, причем каждый такой флаг устанавливается тогда, когда требует об-
служивания какое-либо устройство на этом уровне приоритета. И, наконец,
когда ЦП выставляет BGn, то этот сигнал обнаружат только устройства, на-
250
холящиеся на линии с уровнем приоритета п; на устройства с другими уров-
нями он вообще никак не повлияет.
Как же эта аппаратная схема достигает того, что мы обычно мыслим себе
как расстановка приоритетов? Ответ весьма простой. Когда ЦП завершает
выполнение текущей инструкции, он просматривает свои флаги прерыва-
ний, которых, как нам теперь известно, четыре по порядку, начиная с флага
с наивысшим приоритетом. Таким образом, если, например, два устройства,
одно с уровнем приоритета 4, а другое с уровнем приоритета 6, требуют об-
служивания, ЦП первым проверит флаг уровня 7. Обнаружив, что он сбро-
шен, ЦП перейдет к флагу уровня 6. Поскольку этот флаг установлен, ЦП
выставит сигнал BG6, оставляя тем самым уровень приоритета 4 необслужен-
ным, по крайней мере на данный момент. Следовательно, при этом устрой-
ство с более высоким приоритетом получает обслуживание от ЦП первым.
Приоритетная схема должна давать следующий эффект: если два устрой-
ства с различными уровнями приоритетов генерируют прерывания одновре-
менно, то устройство с более высоким приоритетом должно получать обслу-
живание первым, и его подпрограмма обработки прерываний должна выпол-
няться вплоть до завершения, прежде чем обслуживание получит устройство
с более низким приоритетом. Кроме того, если устройство с более высоким
приоритетом уже сгенерировало прерывание и ЦП выполняет подпрограмму
его обслуживания в момент, когда поступает прерывание от устройства с ме-
нее высоким приоритетом, то последнее прерывание должно игнорировать-
ся до тех пор,тюка не будет полностью обслужено устройство с более высо-
ким приоритетом. Наконец, если ЦП занимается подпрограммой обслужива-
ния устройства с более низким приоритетом, когда поступает прерывание от
устройства с более высоким приоритетом, то выполнение подпрограммы об-
работки прерывания от устройства с более низким приоритетом должно быть
приостановлено, устройство с более высоким приоритетом обслужено, за-
тем должно быть возобновлено выполнение обработчика прерывания от уст-
ройства с более низким приоритетом. К несчастью, описанная до сих пор при-
оритетная схема этого не достигает; фактически в большинстве случаев она
обрабатывает устройства как раз в неверном порядке. Но это в действитель-
ности не является недостатком разработанной до сих пор схемы. Необходи-
мо лишь как-то расширить приоритетную схему, и мы сделаем это в следую-
щем разделе. Чтобы увидеть, почему дела не всегда идут так, как хочется,
рассмотрим простой пример.
Пусть А и В — это два устройства, соответственно с уровнями приоритетов
4 и 6. Предположим, что эти устройства генерируют прерывания одновремен-
но, т. е. в пределах одного и того же цикла выполнения инструкции ЦП. Если
читателю хочется конкретизировать пример с какими-то физическими уст-
ройствами, то он может считать, что А — это терминальная клавиатура, а В —
это сетевые часы. Таким образом, мы полагаем, что клавиша на терминале бы-
ла нажата примерно в тот же самый момент, когда произошел ”тик” часов.
Устройство А выставляет сигнал BR4, а"устройство В — сигнал BR6. Когда
ЦП завершает выполнение текущей инструкции, он проверяет свои флаги
прерываний в порядке 7, 6, 5, 4. Поскольку флаг уровня 6 установлен, ЦП
выставляет сигнал BG6. Устройство В захватывает этот сигнал, передает на-
зад к ЦП сигнал подтверждения и очищает свой сигнал BR6, сбрасывая та-
251
ким образом флаг уровня 6. Затем в ответ на сигнал подтверждения ЦП сбра-
сывает BG6. После этого контроллер устройства В помещает адрес вектора
прерываний этого устройства на шине данных, и ЦП переход к ловушке, ис-
пользуя этот вектор. Таким образом, к этому моменту первоначальные
c(PSW) и с (PC) оказываются помещенными в стек, причем PC содержит ад-
рес первой инструкции подпрограммы обработки прерываний устройства В,
а флаг прерываний уровня 6 оказывается сброшенным. Теперь ЦП готов к
извлечению этой первой инструкции, но прежде чем делать это, он просматри-
вает свои флаги прерываний опять в порядке 7, 6, 5,4. Он видит, что флаги
уровней 7, 6 и 5 сброшены, но флаг уровня 4 все еще установлен — до сих
пор не произошло ничего, что бы его очистило. Поэтому ЦП начинает другую
последовательность прерывания — он посылает сигнал BG4, устройство А
подтверждает его получение и сбрасывает сигнал BR4 (сбрасывая также и
флаг прерываний уровня 4) , адрес вектора прерываний устройства А поме-
щается на шину данных и ЦП выполняет другой переход к ловушке. Теперь
в стек помещаются текущие c(PSW) и с (PC). Но в этом случае помещенное
в стек содержимое PC есть адрес первой инструкции подпрограммы обслужи-
вания устройства В. Новое содержимое PC, которое получается при переходе
к ловушке, является адресом подпрограммы обслуживания устройства А.
Еще раз ЦП готов к выполнению инструкции, а именно, первой инструкции
обработчика прерываний устройства А. Прежде чем делать это, он опять про-
сматривает свои флаги прерываний. На этот раз он находит, что все флаги
сброшены, так что он может начать фактическое выполнение программы.
Но обратите внимание, что ЦП теперь выполняет подпрограмму обработки
прерываний для устройства А, т. е. для устройства с более низким приорите-
том. Если никаких последующих прерываний не произойдет, то выполнение
этой подпрограммы завершится, и инструкция RTI вытолкнет из стека зна-
чения PC и PSW. Что же теперь будет выполняться? Будет возобновлена под-
программа обработки прерываний от устройства В (напоминаем, что реально
она даже никогда не была запущена). Предположительно она достигнет за-
вершения, и последняя инструкция RTI передаст управление обратно к той
части программы, которая выполнялась в момент возникновения этих двух
прерываний. Таким образом, мы видим, что, хотя устройство В имеет более
высокий приоритет, чем устройство А, первой была завершена подпрограмма
обслуживания устройства А. Но это не тот результат, которого мы ищем,
устанавливая приоритетную схему; фактически эффект получается как раз
обратным тому, которого мы добиваемся. Мы оставляем читателю проверить,
что результаты следующих двух примеров являются такими, как описано.
Предположим теперь, что в то время, когда выполняется какая-то про-
грамма, устройство В генерирует прерывание на уровне приоритета 6. Если
никакие другие прерывания не ожидают обработки, то управление будет пе-
редано подпрограмме обработки прерываний устройства В. Предположим
затем, что в то время, как ЦП занимается этой подпрограммой обслужива-
ния , устройство А генерирует прерывание на уровне 4. Центральный процес-
сор остановит выполнение подпрограммы обработчика для уровня 6 и за-
пустит выполнение обработчика для уровня 4. Обработчик для уровня 4 про-
работает до завершения, а затем будет возобновлено и завершится выполне-
ние обработчика устройства В, и, наконец, возобновится выполнение основ-
ной программы с того места, где произошло прерывание (от устройства В).
Но опять этот результат не согласуется с нашей концепцией приоритетов:
устройство с уровнем приоритета 4 не должно иметь возможности прерывать
обработку прерывания от устройства с уровнем 6.
Наконец, предположим, что в процессе выполнения основной программы
устройство А генерирует прерывание на уровне 4 и начинается обслуживание
устройства А. Предположим, что в середине этой обработки устройство В ге-
нерирует прерывание на уровне 6. Как и ранее, управление будет передано
обработчику устройства В, который проработает до завершения, после чего
-будет возобновлена работа обработчика устройства А и, в конце концов, бу-
дет продолжена основная обработка. В этом случае устройство с более высо-
ким приоритетом (устройство В) прервало обслуживание устройства с более
низким приоритетом (устройство А), т. е. желаемый результат был достигнут.
Таким образом, предложенная приоритетная схема — когда ЦП просма-
тривает флаги прерываний, начиная с самого высокоприоритетного, — хотя и
представлялась поначалу многообещающей, на практике оказалась обманчи-
вой. Однако спасти положение можно совсем простым и элегантным спосо-
бом — в дополнение к приоритетам внешних устройств мы назначаем уро-
вень приоритета самому ЦП, причем этот уровень может подстраиваться.
12.8. ПРИОРИТЕТ ЦЕНТРАЛЬНОГО ПРОЦЕССОРА
Уровень приоритета ЦП так же как и в случае устройств, — это число от
О до 7 включительно; он определяется битами от 7-го до 5-го в PSW, как по-
казано на рис. 12.8.1. Этот приоритет может устанавливаться под програм-
мным управлением одним из двух способов. Во-первых, на эти биты можно
воздействовать стандартными аппаратными инструкциями ЭВМ PDP-11 таки-
ми, как MOV, BIS или CLR. Таким образом, например MOV #300, PSW уста-
новит уровень приоритета ЦП равным 6, поскольку установятся биты 7 и 6;
обратите, однако, внимание, что все другие биты этой инструкцией будут
сброшены. Существует также специальная инструкция, единственной функ-
цией которой является установка этих битов. Это инструкция SPL (Set
Priority Level — установить уровень приоритета); она имеет формат SPL п.
При выполнении этой инструкции биты от 7-го до 5-го в PSW будут установ-
лены равными п. (Замечание. Не все модели PDP-11 могут декодировать
инструкцию SPL.)
Второй способ, каким может устанавливаться уровень приоритета ЦП,
определяется как результат какого-либо типа перехода к ловушке — выпол-
нением инструкции наподобие TRAP или EMTu/ш переходом к ловушке, свя-
занным с прерыванием от устройства. Вскоре мы увидим, как можно извле-
кать преимущества из этого действия. Заметим, сначала, что концепция при-
оритета для ЦП отличается от таковой для внешних устройств в следующем
отношении. Уровень приоритета внешнему устройству назначается при под-
ключении его к шине управления на один из уровней запроса шины и предо-
15___________________8 7 6 5 3 210
I Приоритет | ! N ; Z ; V | С
I I । 1 I । । • I ।________।__I I I I_____I__I
Рис. 12.8.1
252
253
ставления шины. Следовательно, приоритет устройства программно изменить
невозможно; его можно изменить лишь с помощью каких-то изменений в
аппаратуре. В то же время приоритет ЦП не является аппаратно-фиксирован-
ным — его можно изменять программно.
Теперь мы, наконец, выявили все, что происходит во время последова-
тельности прерывания. Вспомним, что когда ЦП завершает выполнение ка-
кой-то инструкции, перед началом следующей инструкции он просматрива-
ет свои флаги прерываний. Если он обнаруживает, что ни один из них не уста-
новлен, то переходит к следующей инструкции. Но, предположим, он обна-
руживает, что один из флагов прерываний установлен. В противоположность
( тому впечатлению, которое сложилось у нас к настоящему моменту, процес-
сор не начинает автоматически последовательность прерывания (посылку
сигнала BG, помещение в стек содержимого PSW и PC, переход к ловушке и
т. п.). Вместо этого он сначала проверяет уровень приоритета прерывающего ,
устройства, т. е. уровень запрашивающей линии BR и сравнивает его со своим
собственным приоритетом. Если уровень приоритета прерывающего устрой-
ства выше, чем у процессора, ЦП инициирует последовательность прерыва-
ния, как описано ранее. Однако если запрашивающее устройство имеет
уровень приоритета, который меньше или равен уровню приоритета ЦП, то
ЦП игнорирует запрос и начинает выполнение следующей инструкции. Эти
две простые концепции — то, что приоритет ЦП может устанавливаться под
программным управлением и что прерывающие устройства с уровнями при-
оритета, не превосходящими уровень приоритета ЦП, в существенной степени
игнорируются процессором (мы нередко говорим, что такие прерывания
маскируются) — делают приоритетную схему устройств работоспособной.
Причем эта схема не только работоспособна, но программист получает пол-
ное управление над тем, как, когда и будут ли устройства прерывать основ-
ную обработку или выполнение какой-то другой обслуживающей подпро-
граммы.
Проиллюстрируем эти идеи в некоторых деталях на двух примерах. В
каждом из них будут фигурировать два устройства, генерирующие прерыва-
ния с различными уровнями приоритетов. Одним устройством будут извест-
ные сетевые часы, которые генерируют прерывания на уровне 6. Другим бу-
дет консольная клавиатура, о которой необходимо сказать несколько слов.
Подобно любому другому прерывающему устройству, консольная клавиату-
ра обладает вектором прерываний, размещенным в ячейках 000060 и 000062.
Она также имеет статусный регистр в ячейке 777560, который в существен-
ной степени похож на статусный регистр часов. Бит 6 этого регистра опять
является битом разрешения прерываний. В этом регистре есть некоторые
другие биты, имеющие значение, но для целей нашего примера ими занимать-
ся не придется. Если-бит разрешения прерываний установлен, то контроллер
клавиатуры будет генерировать прерывания каждый раз, когда на клавиату-
ре нажимается клавиша. И если условия подходящие, т. е. если приоритет ЦП
достаточно низкий, то будет инициирована последовательность прерывания и
управление будет передано подпрограмме обработки прерываний от клавиа-
туры. Мы не будем явно говорить о том, что делает эта обслуживающая под-
программа, потому что нас здесь это не касается; нас больше интересует
структура прерывания в этой ситуации. Кроме того, поскольку мы уже при-
254
000100 177546 000060 177560 CLKINT - 100 CLKSTS - 177546 KBINT а 60 KBSTS а 177560 (ИНИЦИАЛИЗИРОВАТЬ ВЕКТОРЫ ПРЕРЫВАНИИ (УСТАНОВИТЬ ПРИОРИТЕТ ПРОЦЕССОРА (РАЗРЕШИТЬ ПРЕРЫВАНИЯ ОТ УСТРОЙСТВ
001000 001000 012767 ."1000 MOV OTICKtCLKINT (УСТАНОВИТЬ АДРЕС ОБСЛУДИВАОйЕИ
001006 001014 006342' 000100 012767 000300 000102 012767 ( ПОДПРОГРАММЫ MOV 4300.CLKINT+2 ( И ПРИОРИТЕТ MOV *KEY»KBINT (АДРЕС ОБСЛУПИВАНИЯ КЛАВИАТУРЫ
001022 001030 007404’ 000060 012767 000200 000062 000230 MOV »20<hKBINT+2 ( И ПРИОРИТЕТ SPL 0 (УСТАНОВИТЬ ПРИОРИТЕТ ЦП = 0
001032 012767 MOV MOOrKBSTS (РАЗРЕДИТЬ ПРЕРЫВАНИЯ ОТ КЛАВИАТУРЫ
001040 000100 177560 012767 MOV S100»CLKSTS (РАЗРЕДИТЬ ПРЕРЫВАНИЯ ОТ ЧАСОВ
001046 001050 001052 001054 001056 001060 006342 006344 006346 006350 006352 006354 006356 007404 007406 007410 007412 007414 007416 007420 000100 177546 000002 000002 (НАЧАЛО ОСНОВНОЙ ОБРАБОТКИ * • • < ХПРОИЗОПЛО ПРЕРЫВАНИЕ ОТ ЧАСОВ) ( (ОБРАБОТЧИК ПРЕРЫВАНИЙ ОТ ЧАСОВ ( TICK: < (ПРОИЗОШЛО ПРЕРЫВАНИЕ ОТ КЛАВИАТУРЫ) RTI (ВЫХОД ИЗ ПРЕРЫВАНИЯ ОТ ЧАСОВ ( (ОБРАБОТЧИК ПРЕРЫВАНИЯ ОТ КЛАВИАТУРЫ KEY: RTI (ВЫХОД ИЗ ПРЕРЫВАНИЯ ОТ КЛАВИАТУРЫ •
Рис. 12.8.2
водили пример подходящей подпрограммы обработки прерываний от часов,
здесь мы ее не показываем.
На рис. 12.8.2 мы полагаем, что во время выполнения основной обработки
происходит прерывание от часов. С помощью обычной последовательности
прерывания управление передается подпрограмме обслуживания часов. В то
время, когда выполняется эта подпрограмма, происходит прерывание от кла-
виатуры. Но в этом случае, в противоположность примеру из последнего раз-
дела, выполнение подпрограммы обслуживания часов будет продолжаться, а
прерывание от клавиатуры окажется эффективно замаскированным. Когда
255
подпрограмма обслуживания часов завершится, процессор займется подпро-
граммой обслуживания клавиатуры, и, в конце концов, возобновится основ-
ная обработка. Это именно те действия, которых мы хотим, если считать ча-
сы более важным устройством, чем клавиатура, поскольку последнему уст-
ройству не удалось прервать подпрограмму обслуживания часов, имеющих
более высокий приоритет. Рассмотрим этот программный сегмент детально.
Первые несколько инструкций из показанных в листинге гарантируют,
что перед началом основной обработки будут правильно инициализированы
различные ячейки. В частности, адрес подпрограммы обслуживания часов,
TICK, помещается в первое слово вектора прерываний от часов (000100), а
число 300 помещается во второе слово (000102). Обратите внимание, что это
действие устанавливает в битах от 7-го до 5-го этого слова значение 110. Ана-
логично, адрес KEY пересылается в ячейку 000060, а слово в ячейке 000062
получает число 200, что устанавливает в битах от 7-го до 5-го этого слова зна-
чение 100. Затем мы гарантируем, что уровень приоритета ЦП будет равен 0
(SPL 0) и разрешаем прерывания от клавиатуры и часов. Теперь начинается
основная обработка.
Мы предполагаем, что, когда ЦП занят обработкой, происходит прерыва-
ние от часов, как это показано в листинге. Завершив выполнение текущей ин-
струкции в ячейке 001054, ЦП просматривает флаги прерываний в порядке
7, 6, 5, 4. Он обнаруживает, что флаг уровня 7 сброшен, но флаг уровня 6
установлен, поскольку часы выставили сигнал BR6. Сравнивая уровень прио-
ритета 6 со своим собственным уровнем, который равен 0, ЦП выявляет, что
запрос происходит на более высоком уровне, чем его собственный, и, следо-
вательно, инициирует последовательность прерывания. В частности, он отве-
чает на запрос шины BR6, выставляя сигнал BG6. Часы обнаруживают BG6 и
подтверждают его, очищая в то же самое время сигнал BR6. Затем ЦП сраба-
тывает BG6, а часы помещают адрес первого слова своего вектора прерыва-
ний, 000100, на шину данных и уведомляют об этом ЦП сигналом INTR на
шине управления. Центральный процессор проталкивает в стек текущее
c(PSW) (значение которого для нашего обсуждения несущественно), а вслед
за ним — текущее с(РС), значение которого равно 001056. Затем в PC поме-
щается содержимое ячейки памяти 000100, а именно 006342 = TICK. В PSW
помещается содержимое ячейки памяти 000102 (000300). Таким образом,
обработка продолжается в подпрограмме обслуживания часов.
Предположим теперь, что во время выполнения инструкции в ячейке
006344 на консольном терминале нажимается клавиша. Контроллер клавиа-
туры генерирует прерывание на уровне приоритета 4. Когда ЦП завершает
выполнение своей текущей инструкции, он просматривает флаги прерыва-
ний. Он видит, что флаги уровней 7, 6 и 5 сброшены, но флаг уровня 4 уста-
новлен. Центральный процессор сравнивает этот уровень приоритета со сво-
им собственным и находит, что его уровень приоритета равен 6. Это явилось
результатом того, что PSW получило новое значение 000300 из ячейки 000102.
Биты приоритета, от 7-го до 5-го, установлены в 110 и, следовательно, ЦП в
данный момент работает на уровне приоритета 6. Поскольку 4 не больше,
чем 6, ЦП игнорирует запрос на обслуживание от клавиатуры и продолжает
выполнять подпрограмму обслуживания часов. Такой просмотр флагов пре-
рываний и игнорирование запроса на уровне 4 имеют место перед началом
выполнения каждой из оставшихся инструкций в подпрограмме обслужи-
вания часов.
Наконец, подпрограмма обслуживания часов выполняет инструкцию RTL
Верхний элемент стека выталкивается в PC (вспоминаем, что на вершине сте-
ка был адрес 001056) и из стека также выталкивается первоначальное содер-
жимое PSW. Таким образом, готова возобновиться основная обработка в
ячейке 001056. Однако перед выполнением инструкции по этому адресу ЦП
просматривает флаги прерываний и обнаруживает, что флаг уровня 4 уста-
новлен. Поскольку его собственный приоритет теперь равен 0 (ведь это зна-
чение было восстановлено, когда было вытолкнуто из стека первоначальное
содержимое PSW), ЦП опять инициирует последовательность прерывания. Не,
вдаваясь во все детали, мы знаем, что текущее c(PSW) (значение которого
опять не представляет интереса за исключением того, что биты приоритета
равны 000) проталкивается в стек, текущее с (PC) (001056) тоже проталки-
вается в стек, PC получает новое содержимое из ячейки 000060 (которая со-
держит 007404 = KEY), а новое содержимое PSW (с битами приоритета
100) — из ячейки 000062. Таким образом, ЦП теперь выполняет подпрограм-
му обслуживания клавиатуры и работает на уровне приоритета 4. Поскольку
дальнейших прерываний не происходит, эта подпрограмма обслуживания до-
стигает завершения, выполняет RTI, в результате чего восстанавливаются пер-
воначальные с(РС) (001056) и c(PSW). Итак, при отсутствии дальнейших
прерываний, наконец, возобновляется основная обработка.
Наш следующий пример (рис. 12.8.3) представляет собой некоторую вари-
ацию предыдущего, и, хотя он в равной степени интересен и выявляет суще-
ство дела, мы рассмотрим его несколько более поверхностно. Первоначаль-
ные условия предполагаем такими же, как и прежде, но на этот раз во время
основной обработки происходит прерывание от клавиатуры. Управление пе-
редается подпрограмме обслуживания клавиатуры, и, когда эта подпрограм-
ма выполняется, происходит прерывание от часов. Как видим, на этот раз
управление передается подпрограмме обслуживания прерываний от часов,
которая выполняется (предположительно обнаруживает тик часов и коррек-
тирует время) и затем возвращает управление подпрограмме обслуживания
клавиатуры. Наконец, после завершения обслуживания клавиатуры возоб-
новляется основная обрабстка. Здесь опять мы получаем желаемый эффект.
Несмотря на то, что прерывание от клавиатуры вызвало начало обслужива-
ния клавиатуры, прерывание от часов было обслужено незамедлительно —
более важному устройству была дана возможность прервать обслуживание
менее важного устройства.
Как и в предыдущем примере, инициализируются векторы прерываний и
статусные регистры устройств, а уровень приоритета процессора устанавлива-
ется равным 0. Начинается основная обработка, и мы полагаем, что как раз
перед выполнением инструкции, размещенной в ячейке 001056, происходит
прерывание от клавиатуры. Центральный процессор подтверждает это преры-
вание, помещает в стек текущие c(PSW) и с (PC) (001056), получает новое
с (PC) (007404 = KEY) из ячейки 000060 и новое с (PSW) (000200) из ячейки
000062. Обработка, таким образом, продолжается с начала подпрограммы
обслуживания клавиатуры, и, поскольку текущее содержимое PSW теперь
имеет значение 000200, ЦП работает на уровне приоритета 4. В то время, ко-
256
9 Зак. 2212
257
000100 CLKINT = 100
177546 000060 177560 001000 012767 CLKSTS = 177546 KBIMT = 60 KBSTS « 177560 f ^ИНИЦИАЛИЗИРОВАТЬ ВЕКТОРЫ ПРЕРЫВАНИИ FУСТАНОВИТЬ ПРИОРИТЕТ ПРОЦЕССОРА ГРАЗРЕИИТЬ ПРЕРЫВАНИЯ ОТ УСТРОЙСТВ : MOV 4TICK.CLKINT ;УСТАНОВИТЬ АДРЕС 0БСЛУ1ИВАНИЯ
006342’ 000100 001006 012767 000300 000102 001014 012767 MOV »300rCLKINT*2 F И ПРИОРИТЕТ MOV ’KEY.KBINT FАДРЕС ОБСЛУЖИВАНИЯ КЛАВИАТУРЫ
007404’ 000060 001022 012767 000200 000062 001030 000230 MOV 1200.KBINT+2 1 И ПРИОРИТЕТ SPL 0 1УСТАНОВИТЬ ПРИОРИТЕТ ЦП = 0
001032 012767 MOV J100.KBSTS РАЗРЕШИТЬ ПРЕРЫВАНИЯ ОТ КЛАВИАТУРЫ
000100 177560 001040 012767 MOV 1100, CLKSTS ?РАЗРЕШИТЬ ПРЕРЫВАНИЯ ОТ ЧАСОВ
000100 177546 001046 001050 001052 001054 001056 001060 006342 TICKs 006344 006346 006350 006352 000002 006354 006356 007404 KEY: 007406 007410 007412 007414 000002 007416 007420 Рис. 12.8.3 F ;НАЧАЛО ОСНОВНОЙ ОБРАБОТКИ 9 • • < (ПРОИЗОШЛО ПРЕРЫВАНИЕ ОТ КЛАВИАТУРЫ) F ;ОБРАБОТЧИК ПРЕРЫВАНИЙ ОТ ЧАСОВ Г RTI ;ВЫХОД ИЗ ПРЕРЫВАНИЯ ОТ ЧАСОВ • ь F ОБРАБОТЧИК ПРЕРЫВАНИЙ ОТ КЛАВИАТУРЫ Г < (ПРОИЗОШЛО ПРЕРЫВАНИЕ ОТ ЧАСОВ) RTI ;ВЫХОД ИЗ ПРЕРЫВАНИЯ ОТ КЛАВИАТУРЫ
гда ЦП выполняет эту подпрограмму, часы генерируют прерывание непосред-
ственно перед выполнением инструкции, расположенной в ячейке 007410.
Прежде чем извлечь инструкцию из ячейки 007410, ЦП просматривает свои
флаги прерываний и обнаруживает,что флаг уровня 6 установлен. Сравнивая
этот уровень приоритета со своим собственным (4), он обнаруживает, что
это новое прерывание должно быть удовлетворено, поскольку его уровень
приоритета превосходит собственный уровень процессора. Таким образом,
c(PSW) текущей подпрограммой обслуживания клавиатуры помещается в
стек, а также в стек проталкивается текущее с (PC) (007410). Новые с (PC)
258
и c(PSW) получаются из ячеек 000100 и 000102 со значениями 006342 и
000300 соответственно, и начинается работа подпрограммыобслуживания ча-
сов. Читателю не составит труда увидеть, что при отсутствии дальнейших пре-
рываний, когда выполнение подпрограммы обслуживания часов достигнет
завершения, возобновится обслуживание клавиатуры, а после его заверше-
ния будет продолжена основная обработка, которая была прервана в ячейке
001056.
Необходимо прокомментировать те числа, которые мы назначаем второму
слову вектора прерываний каждого из этих устройств. Когда устройство за-
прашивает прерывание и это прерывание удовлетворяется ЦП, находящееся
во втором слове вектора прерываний число помимо прочего будет опреде-
лять приоритет, с которым ЦП будет выполнять подпрограмму обслужива-
ния этого устройства. Содержимое этого слова становится словом состояния
процессора, а биты от 7-го до 5-го этого слова определяют приоритет ЦП. Чи-
татель может заметить, что в векторе часов эти биты установлены равными
6, а это как раз и есть уровень, на котором происходят прерывания от часов.
Аналогично, для клавиатуры, устройства с уровнем приоритета 4, биты от
7-го до 5-го его слова вектора в KBINT + 2 были установлены равными 4. Бы-
ло ли это сделано просто случайно или произвольно? Нет. Но так же и не обя-
зательно устанавливать биты приоритета во втором слове вектора прерыва-
ния устройства равными уровню приоритета самого устройства. Для любого
из этих устройств мы могли бы установить эти биты так, как пожелали бы.
Но мы покажем, что они были установлены разумно.
Когда выполняется подпрограмма обслуживания устройства, ЦП работает
на том уровне приоритета, который был определен битами приоритета второ-
го слова в векторе прерываний этого устройства. Смысл здесь в том, что, ко-
гда обслуживается какое-то устройство, устройства с более высоким приори-
тетом должны иметь возможность прервать это обслуживание, а устройства с
более низким приоритетом не должны иметь такой возможности. Что можно
сказать по поводу устройств, пытающихся выполнить прерывания на одном
и том же уровне приоритета? Представляется, что нет какой-либо неотрази-
мой причины, чтобы предоставлять им немедленное обслуживание, ведь они
не более важны, чем то устройство, обслуживание которого происходит;
поэтому мы оставляем их ждать, пока не будет завершена текущая обработ-
ка устройства.
Некоторые ЭВМ располагают более изощренными структурами аппарат-
ных приоритетов, и в таких случаях назначение приоритетов устройствам
должно обрабатываться как-то по-иному. Предложения в этом направлении
читатель найдет в нескольких упражнениях. На тех ЭВМ, где, как и на PDP-11,
приоритет устройства ’’зашивают” аппаратно, у нас не остается возможности
выбора уровня, на котором происходит прерывание от устройства — он пред-
определяется производителем ЭВМ. В некоторых случаях эти предварительно
установленные приоритеты могут не согласовываться с нашими целями.
В качестве примера рассмотрим систему ЭВМ PDP-11, поддерживающую
консольную клавиатуру и считыватель перфоленты, причем оба эти устрой-
ства подключены к'ЭВМ на уровне прерываний 4. По каким-то причинам нам
может потребоваться отдать большее подпочтение считывателю перфоленты,
вероятно, из-за того, что это медленно работающее устройство, а считывае-
9* 259
мые с него данные необходимы для нашей программы, и мы не хотим, чтобы
его обслуживание подвергалось задержке, предпочитая задерживать, если
нужно, обслуживание клавиатуры. Но если следовать описанной только что
схеме определения числа во втором слове вектора прерываний устройства,
т. е. сделать это слово равным 200, то, когда будет выполняться подпрограм-
ма обслуживания прерываний от любого из этих устройств, Щ1 будет рабо-
тать на уровне приоритета 4, и другое устройство не сможет прервать его ра-
боту. И хотя мы не хотим, чтобы клавиатура прерывала обработку перфолен-
ты, мы все-таки намереваемся дать возможность считывателю перфоленты
прерывать обработку прерываний от клавиатуры.
Простой способ сделать это — в смысле преодоления намерений произво-
дителя ЭВМ — поместить число 000200 во второе слово вектора прерываний
считывателя перфоленты. Тогда выполнение программы его обслуживания
будет происходить на уровне приоритета 4. Но в векторе прерываний клави-
атуры второе слово нужно установить равным 000140, из чего следует, что
подпрограмма обслуживания прерываний от клавиатуры будет выполняться
на уровне приоритета 3. Делая так, мы не меняем уровней, на которых про-
исходят прерывания от устройств, да фактически мы и не можем их изме-
нить. Вместо этого мы устанавливаем, что подпрограммы их обслуживания
будут выполняться с уровнями приоритета ЦП соответственно 4 и 3.
Предположим теперь, что выполняется подпрограмма обслуживания счи-
тывателя перфоленты и происходит прерывание от клавиатуры на уровне 4.
Поскольку ЦП также имеет уровень приоритета 4, прерывание будет замас-
кировано, как мы того и хотели. В то же время, если выполняется подпро-
грамма обслуживания клавиатуры, когда уровень приоритета ЦП равен 3,
и происходит прерывание от считывателя перфоленты на уровне 4, то это пре-
рывание будет удовлетворено — управление, как нам того и хотелось, будет
передано подпрограмме обслуживания считывателя перфоленты. Таким об-
разом, хотя изменить фактические аппаратные приоритеты различных уст-
ройств мы не в состоянии, мы все же обладаем изрядной возможностью
управлять тем, какие устройства будут генерировать прерывания, а какие —
нет, и мы можем заставить выполняться подпрограммы их обслуживания в
том порядке, какой выберем.
Если два устройства генерируют прерывания одновременно и если эти
прерывания происходят на разных уровнях, то, конечно, устройство с более
высоким приоритетом получает обслуживание от ЦП первым. Если же, од-
нако, они генерируют прерывания на одном и том же уровне, то первым по-
лучает обслуживание то устройство, которое первым обнаруживает сигнал
BG от ЦП, т. е. устройство, которое физически ближе к ЦП на шине управ-
ления.
12.9. КОНСОЛЬНЫЙ ТЕРМИНАЛ
Этот раздел не является основой для дальнейшего изложения и, следовательно, мо-
жет быть опущен. В то же время у добросовестного читателя может появиться желание
познакомиться подробнее с консольным терминалом, чтобы увеличить степень своего
понимания работы внешних устройств ЭВМ и предоставляемых ими возможностей.
В этом смысле терминал является устройством более представительным, чем сетевые
часы; он демонстрирует некоторые особенности, которые мы еще не обсуждали.
260
ЦП
Флаги
прерываний
<^^Адресная шин7
^^Шина данных"
Шина управления
Статусный
регистр
Регистр данных
Рис. 12.9.1
Типичный используемый сегодня терминал — это фактически два устройства: клави-
атура (устройство ввода) и печатающее устройство (устройство вывода). Это напоми-
нает электрическую пишущую машинку, но эти два устройства ни механически, ни элек-
трически между собой не связаны. Единственная причина рассматривать их как некий
единый узел обусловлена тем, что они размешаются в одном корпусе и зачастую сов-
местно используют общий контроллер устройства, который фактически представляет
собой два отдельных контроллера. Несмотря на это, клавиатура и печатающее устрой-
ство очень часто довольно тесно взаимодействуют через программное обеспечение, а
их поведение по отношению к соответствующим контроллерам весьма сходно. По этой
причине для представления этих устройств мы используем общий рисунок (рис. 12.9.1).
Его надо интерпретировать как представляющий или клавиатуру, или печатающее уст-
ройство (но не оба одновременно). Двойная стрелка между терминалом и контролле-
ром показывает, что данные могут проходить или от клавиатуры к контроллеру и, сле-
довательно, к различным шинам, или от контроллера к печатающему устройству.
В предыдущем разделе мы упоминали контроллер клавиатуры, и, в частности, его
статусный регистр. Теперь мы познакомимся с этим контроллером более подробно.
С клавиатурой ассоциируются два 16-битрвых регистра: статусный регистр и регистр
данных, называемый иногда буферным регистром. Эти регистры вместе с назначенными
им адресами показаны на рис. 12.9.2. В зависимости от типа терминального устройства
статусный регистр клавиатуры может содержать еще какие-то биты, имеющие значение,
но для наших целей мы можем их игнорировать без всяких последствий. Регистр дан-
ных — простая структура: его младший байт содержит код ASCII знака, представляюще-
го последнюю клавишу, которая была нажата на клавиатуре. Статусный регистр также
довольно прост, особенно в свете нашего опыта со статусным регистром часов. Следует,
конечно, ожидать, что бит доступности данных устанавливается всякий раз, когда в ре-
гистре данных содержатся действительные данные. Это совершенно правильно, но здесь
есть некоторые специальные соглашения, которые оправдывают наше исследование.
261
15
Статусный ГХ
регистр
клавиатурыкхз
8 7 6
О
Адрес =
777560
Бит доступности данных
Бит разрешения прерываний---
Данные
Рис. 12.9.2
Когда на клавиатуре нажимается какая-то клавиша, то происходят следующие собы-
тия. Логическая схема клавиатуры генерирует код ASCII соответствующего знака и по-
сылает этот восьмибитовый (или иногда семибитовый) код в регистр данных контрол-
лера. Когда регистр данных заполняется, контроллер обнаруживает это и устанавливает
бит доступности данных в статусном регистре. Если бит разрешения прерываний в ста-
тусном регистре установлен, то контроллер пошлет сигнал BR (запроса шины) по линии
BR4 шины управления. Если бит разрешения прерываний сброшен, то никакого преры-
вания генерироваться не будет.
Предположим, что бит разрешения прерываний в статусном регистре клавиатуры
установлен, когда нажимается клавиша на клавиатуре. Тогда код будет передан в ре-
гистр данных, бит доступности данных станет установленным, и будет сгенерировано
прерывание. Поскольку это прерывание будет в конце концов подтверждено (если толь-
ко уровень приоритета процессора не окажется равным 4 или выше), то будет выпол-
нена подпрограмма обслуживания клавиатуры. Но предположим, что эта подпрограмма
не располагает средствами, чтобы сбросить бит доступности данных. Что случится, ко-
гда будет нажата другая клавиша? Код.этой клавиши будет передан в регистр данных и
перекроет то, что там было первоначально, а также будет установлен бит доступности
данных. Но поскольку этот бит уже был установлен, он просто таким и останется. Будет
ли сгенерировано еще одно прерывание? Как это ни парадоксально — нет. Чтобы понять,
почему это так, мы должны посмотреть, что же порождает прерывание. Прерывание
не порождается тем фактом, что оба бита разрешения прерываний и доступности данных
установлены. Оно происходит, когда бит разрешения прерываний установлен, а бит до-
ступности данных меняет свое состояние с 0 на 1. Именно этот переход бита доступности
данных от нижнего уровня к верхнему при наличии установленного бита разрешения
прерываний вызывает прерывание. Таким образом, если бит доступности данных не был
очищен после того, как был напечатан какой-то знак, то следующее нажатие клавиши
прерывания не сгенерирует.
Как же сбрасывается бит доступности данных в статусном регистре клавиатуры?
Этот бит будет очищаться при любом обращении к регистру данных клавиатуры. Обыч-
но, когда происходят программные обнаружения прерываний от клавиатуры, подпро-
грамма обслуживания клавиатуры получает код напечатанного знака и помещает его ку-
да-то, вероятно в стек. Следовательно, подпрограмма обслуживания может содержать
инструкцию наподобие MOVB KBDATA, -(R5), где KBDATA = 177562. Давайте посмо-
трим, что происходит, когда выполняется такая инструкция. Центральный процессор на-
меревается прочитать содержимое этого регистра, чтобы его можно было поместить в
стек. Чтобы это сделать, он помещает данный адрес, а именно 177562, на адресную шину
и посылает сигнал READ по шине управления. Каждое устройство на шине обнаружива-
ет этот управляющий сигнал и просматривает адрес на адресной шине. И каждое устрой-
ство задается вопросом: не является ли этот адрес адресом одного из моих регистров?
Если ответ отрицательный, то устройство игнорирует сигнал READ. Утвердительно на
262
Статусный Д,®
регистр
печатающего L
устройства
Бит готовности-
8 7 6
О
Я Адрес =
3 777564
Бит разрешения прерываний------
Регистр
данных
печатающего
устройстве
J-1
Адрес =
777566
Данные
8 7
О
Рис. 12.9.3
этот вопрос ответит только одно устройство, а именно клавиатура. Контроллер поме-
стит содержимое представляющего интерес регистра (KBDATA) на шину данных и по-
шлет назад к ЦП сигнал подтверждения, чтобы показать, что запрошенные данные до-
ступны на шине данных. В то же самое время контроллер сбрасывает бит 7 статусного
регистра — бит доступности данных. Это именно тот эффект, который необходим нам
для правильной работы с этим устройством.
Могли бы мы точно так же сбросить бит 7 с помощью инструкции типа MOV #100,
KBSTS или BIC #200, KBSTS (где KBSTS= 1777560) и в то же самое время оставить
бит разрешения прерываний (бит 6) установленным? Интересно будет узнать, что ответ
отрицательный. Хотя прочитать этот бит мы, конечно, можем — мы делаем это каждый
раз, когда считываем содержимое статусного регистра, но записан (т. е. установлен из 0
в 1) этот бит может быть только контроллером. По этой причине бит 7 называют только
читаемым битом, в противоположность биту 6, который называется записываемым и чи-
таемым битом.
Хотя в следующем примере мы используем печатающее устройство, говорить о нем
здесь много не будем. Его регистры показаны на рис. 12.9.3. Поскольку работать с печа-
тающим устройством мы будем без использования прерываний, то нам необходимо
только определить, как можно распечатать знак, а это задача совсем простая. Чтобы рас-
печатать данный знак, нам необходимо только переслать код ASCH этого знака в регистр
данных печатающего устройства, что запустит процесс распечатки и в то же самое время
сбросит бит готовности в статусном регистре. Таким образом, чтобы распечатать знак
без использования прерываний, мы можем просматривать бит готовности до тех пор, по-
ка не увидим, что он установлен, т. е. что печатающее устройство готово принять следу-
ющий знак, и затем перешлем код ASCII желаемого знака в регистр данных.
Идея, положенная в основу программного сегмента на рис. 12.9.4, весьма проста. Мы
полагаем, что идет выполнение программы пользователя, но во время этого выполнения
на клавиатуре нажимается одна или несколько клавиш. Хотя, вероятно, в данный мо-
мент с этим вводом нам делать нечего, мы можем, по крайней мере, сохранить его для
обработки позднее. Знаки сохраняются в 2561О — байтовой области памяти,.называемой
буфером и имеющей метку BUFFER. Вспоминаем, что клавиатура и печатающее устрой-
ство между собой как устройства ничем не связаны; простое нажатие клавиши не приве-
дет к тому, что знак появится на печатающем устройстве. Поэтому подпрограмма обслу-
живания клавиатуры обеспечивает прием знака, размещение его в буфере и затем пере-
дачу кода этого знака на печатающее устройство, с которым мы работаем без использо-
вания прерываний. Этот процесс называется выдачей эхо на знак. Мы должны также
знать, что, когда на клавиатуре нажимается клавиша возврата каретки и выдается эхо
на печатающее устройство, печатающий элемент возвращается к левому краю, но в про-
тивоположность тому, что происходит в электрической пишущей машинке, бумага на
следующую строку ие переводится. Следовательно, мы должны обеспечить, чтобы обра-
ботчик клавиатуры обнаруживал возврат каретки и выдавал отсутствующий перевод
строки.
263
000015 CR 15 (КОД ASCII ВОЗВРАТА КАРЕТКИ
000012 LF = 12 JКОД ASCII ПЕРЕВОДА СТРОКИ
000060 KBINT = 60 (АДРЕС ВЕКТОРА ПРЕРЫВАНИЯ КЛАВИАТУРЫ
177560 KBSTS * 177560 (АДРЕС СТАТУСНОГО РЕГИСТРА КЛАВИАТУРЫ
177562 KBDATA » KBSTS+2 (АДРЕС РЕГИСТРА ДАННЫХ КЛАВИАТУРЫ
177564 FIRSTS = 177564 (АДРЕС СТАТУСНОГО РЕГИСТРА ПЕЧАТАВШЕГО УСТРОЙСТВА
177566 PRDATA = PTRSTS+2 (АДРЕС РЕГИСТРА ДАННЫХ ПЕЧАТАВШЕГО УСТРОЙСТВА
177776 PSW 177776 г (АДРЕС PSW
001000 BUFFER» .BLKB 256. } (БУФЕР ДЛЯ ВВОДА С КЛАВИАТУРЫ
001400 012703 001000' MOV »BUFFER>R3 • ИНИЦИАЛИЗИРОВАТЬ АДРЕС БУФЕРА
001404 012767 004726’ 000060 MOV «KEbKBINT (УСТАНОВИТЬ АДРЕС ВЕКТОРА
001412 012767 000200 000062 NOV *200:KBINT+2 ( И PSW
001420 012767 000100 177560 MOV «100>KBSTS (РАЗРЕШИТЬ ПРЕРЫВАНИЯ ОТ КЛАВИАТУРЫ
001426 005067 177564 CLR PTRSTS (ЗАПРЕТИТЬ ПРЕРЫВАНИЯ ОТ ПЕЧАТАВШЕГО УСТРОЙСТВА
001432 005067 177776 CLR PSW (ПРИОРИТЕТ ЦП 0
(
(НАЧАЛО ОСНОВНОЙ ОБРАБОТКИ
(
;ПОДПРОГРАММА ОБСЛУКИВАНИЯ КЛАВИАТУРЫ
004726 116723 177562 КЕТ: MOVB KBDATA:<R3> + (ЗАНЕСТИ ЗНАК В БУФЕР
004732 105767 177564 ECHO: TSTB PTRSTS (ПЕЧАТАВШЕЕ УСТРОЙСТВО ЗАНЯТО?
004736 100375 BPL ECHO (ДА — ПРОВЕРИТЬ СНОВА
004740 116367 177777 177566 MOVB -1<R3>:PRDATA (ВЫДАТЬ 3X0 НА ЗНАК
004746 126327 177777 000015 CMPB -1<R3)»«CR (ЗТО БЫЛ ВОЗВРАТ КАРЕТКИ?
004754 001006 BNE RETURN (НЕТ — ВОЗВРАТ
004756 105767 177564 EOL: TSTB PTRSTS (ПЕЧАТАВШЕЕ УСТРОЙСТВО ЗАНЯТО?
004762 100375 BPL EOL (ДА — ПРОВЕРИТЬ СНОВА»
004764 112767 000012 177566 MOVB ♦LF»PRDATA ( ИНАЧЕ ~ ВЫВЕСТИ ПЕРЕВОД СТРОКИ
004772 000002 RETURN: RTI (ВЕРНУТЬСЯ К ОСНОВНОЙ ОБРАБОТКЕ
Рис. 12.9.4
Зная эти основы, читатель не встретит особых трудностей при прослеживании приме-
ра. Первые несколько строк устанавливают R3 в качестве указателя на буфер, где будут
сохраняться знаки, инициализируют вектор прерываний клавиатуры, разрешают преры-
вания от клавиатуры и запрещают прерывания от печатающего устройства, а также уста-
навливают уровень приоритета процессора равным 0. (Вместо инструкции CLR PSW
можно было бы использовать также SPL 0.) Прерывания от клавиатуры передают управ-
ление подпрограмме KEY, где на знак выдается эхо, и он сохраняется в буфере. Напоми-
наем читателю, что это не полная программа, поэтому многое в ней не обеспечено. На-
пример, мы не видим инструкций для просмотра буфера и воздействия на его содержи-
мое, а также никаких средств для обработки возможного переполнения буфера. Но тем
не менее пример выполняет свою основную цель: показать, как можно каким-то разум-
ным способом обходиться с прерываниями от клавиатуры.
264
000015 000012 177564 177566 000064 CR « 15 LF « 12 PTRSTS « 177564 PRBATA = 177566 PTRINT = 64 1К0Д ASCII ВОЗВРАТА КАРЕТКИ ;код ASCII ПЕРЕВОДА СТРОКИ .АДРЕС СТАТУСНОГО РЕГИСТРА ПЕЧАТАЙЧЕГО УСТРОЙСТВА ;АДРЕС РЕГИСТРА ДАННЫХ ПЕЧАТАВШЕГО УСТРОЙСТВА ;АДРЕС ВЕКТОРА ПРЕРЫВАНИЙ
001000 012767 010226’ 000064 MOV ♦PRINT.PTRINT 1 УСТАНОВИТЬ АДРЕС ОБРАБОТКИ ПРЕРЫВАНИИ
001006 012767 000200 000066 MOV ♦200.PTRINT+2 ; И PSU
001014 000230 SPL 0 1 УСТАНОВИТЬ ПРИОРИТЕТ ЦП
1НАЧАЛ0 ОСНОВНОЙ ОБРАБОТКИ
?
001016
003406 012700 016442* NOV ♦TEXT.RO ;УСТАНОВИТЬ АДРЕС СООБЩЕНИЯ
003412 012767 000100 177564 MOV ♦100.PTRSTS 5 И ЗАПУСТИТЬ РАСПЕЧАТКУ
003420 а ’ПРОДОЛЖЕНИЕ ОБРАБОТКИ
010226 105710 PRINTS TSTB (RO) fСООБЩЕНИЕ СДЕЛАНО?
010230 001402 ВЕО RETURN JДА — ПРОПУСТИТЬ
010232 112067 177566 MOVB (RO)+>PRDATA ;ПОСЛАТЬ ОЧЕРЕДНОЙ ЗНАК
010236 000002 RETURNS RTI ;ВЕРНУТЬСЯ К ОБРАБОТКЕ
016442 124 TEXTS '.ASCIZ /TEST MESSAGE/(CRXLF)
016443 105
016444 123
016445 124
016446 . 040
016447 115
016450 105
016451 123
016452 123
016453 101
016454 107
016455 105
016456 015
016457 012
016460 ООО .EVEN
016462
Рис. 12.9.5
Наш последний пример (рис. 12.9.5) — опять программный сегмент, а не полная про-
грамма - иллюстрирует обслуживание консольного печатающего устройства при исполь-
зовании прерываний. Мы полагаем, что в процессе выполнения программы возникает
необходимость послать сообщение какого-то типа, вероятно какой-то индикатор ошиб-
ки или информационное сообщение (например, текущее время). Условия могут ока-
заться такими, что возможно продолжение обработки во время распечатки сообщения.
Таким образом, мы начинаем распечатку, передавая первый знак сообщения, и возвра-
щаемся назад к основной обработке. После Того как первый знак передан, печатающее
устройство прервет обработку, мы пошлем следующий знак и возобновим обработку и
т. д. до тех пор,-пока не будет передано полное сообщение.
Первые несколько инструкций примера, как обычно, выполняют некоторую иници-
ализацию, после чего начинается основная обработка. Когда возникает необходимость
послать сообщение (в ячейке 003406), мы помещаем адрес первого байта сообщения в
регистр R0, который будет использоваться в качестве указателя для пошагового про-
хождения по знакам сообщения. Но следующая инструкция в ячейке 003412 а именно
265
MOV #100, PTRSTS, требует некоторых пояснений. Хотя должно быть ясно, что эта ин-
струкция разрешает прерывания от печатающего устройства, т. е. устанавливает бит 6
статусного регистра, в комментарии, который сопровождает эту инструкцию, сказано,
что она фактически запускает процесс распечатки. В действительности, это так и есть,
но чтобы это понять, мы должны более детально проследить за работой устройства и
его контроллера.
Когда бит разрешения прерываний установлен, печатающее устройство ведет себя
аналогично клавиатуре. Когда бит готовности переходит из нижнего уровня в верхний,
происходит прерывание (см. рис. 12.9.3). Различие между этими двумя устройствами
заключаются в их нормальном состоянии. Нормальное состояние клавиатуры - "данные
не готовы". То есть, пока не будет нажата клавиша и не будет обработан устройством
ввод знакового кода, бит доступности данных (бит 7) остается сброшенным, и это его
обычное состояние. Однако бит 7 в статусном регистре печатающего устройства — это
бит готовности, и готовность .(принять следующий знак для распечатки) - это нормаль-
ное состояние печатающего устройства. Действительно, в нашем примере печатающее
устройство находилось в состоянии готовности, поскольку предположительно оно не ис-
пользовалось перед тем, как мы решили к нему обратиться. В результате выполнения
инструкции MOV #100, PTRSTS в младшем байте статусного регистра устанавливается
битовая конфигурация 0 1 0 0 0 0 0 0. Но поскольку печатающее устройство не исполь-
зуется, контроллер постоянно устанавливает бит 7 этого регистра. И теперь, когда бит
разрешения прерываний (бит 6) установлен, генерируется прерывание. Хотя такое по-
ведение устройства может показаться причудливым, но пока мы о нем знаем, мы мо-
жем с ним справляться и даже, как в данном случае, извлекать из этого преимущества.
Сама подпрограмма обработки прерываний весьма проста. Сначала проверяем, не яв-
ляется ли знак, который предполагается передать на печатающее устройство знаком
NUL, означающим "конец сообщения”. Если нет, то знак передается, содержимое указа-
теля сообщения (R0) увеличивается на 1, а управление немедленно возвращается к ос-
новной обработке. Некоторое время спустя (обычно через 1/30 секунды, что зависит от
типа терминального устройства) печатающее устройство опять окажется в состоянии го-
товности, сгенерирует другое прерывание и т. д. В конце концов, когда подпрограмма
обслуживания печатающего устройства обнаруживает NUL, она просто выполняет воз-
врат. Поскольку в регистр данных печатающего устройства в этом случае никакого зна-
ка помещено не было, дальнейшие прерывания генерироваться не будут.
Из этого раздела мы можем вывести два заключения о том, что не все устройства
одинаковы как в отношении передачи данных, так и по своей структуре прерываний.
Например, вспомним, что клавиатура не генерирует прерываний, пока ее бит доступ-
ности данных остается сброшенным, но для часов не составляет труда прервать ЦП, даже
если их контрольный бит никогда не сбрасывается. И хотя производители ЭВМ PDP-11,
очевидно, предпринимают сознательные усилия для достижения какого-то уровня стан-
дартизации (например, по статусным регистрам), различные устройства строятся для
выполнения разных функций, и поэтому каждое из них представляет собой в определен-
ной степени особый случай. Короче говоря, если мы хотим использовать и программиро-
вать какое-то конкретное устройство, то должны познакомиться со всеми его особен-
ностями.
Обслуживание устройств — это вопрос нетривиальный, требующий большого внима-
ния и учета деталей, а также потенциально создающий проблемы таймирования, когда
прерывания от устройства появляются в неожиданные момента времени. Это одна из
причин, почему мы использовали макроинструкции $IN.DEC, SOUT. ASC и т. п. Ведь не-
реально ожидать, чтобы начинающий программист на языке ассемблера умел справлять-
ся со всеми сложностями устройств до того, как он (или она) достигнет того уровня
искушенности в аппаратуре и программном обеспечении, который подразумевается в
этой главе. Есть и еще одна причина, почему мы используем эти макроинструкпии. Не-
которые операционные системы ЭВМ, особенно системы с разделением времени, очень
266
заботятся о сохранении своей целостности. Например, при выполнении программы,
прежде чем ЦП обратится к какому-то адресу, система может проверять, что этот адрес
не выходит, скажем, за положенные границы. Если же адрес вся же выходит за эти гра-
ницы, система чаше всего преждевременно выбрасывает неправильную инструкцию и
может просто остановить выполнение всей программы. Регистры устройств обычно име-
ют такие выходящие за положенные границы адреса. Описанные в приложении Б макро-
инструкции работают в тесном взаимодействии с операционной системой, что гарантиру-
ет выполнение только приемлемых инструкций.
Несмотря на очевидные сложности, сопряженные с обслуживанием устройств йа ап-
паратном уровне, мы должны отметить, что ЭВМ PDP-11 — такая машина, архитектура
которой делает эту работу особенно простой. На других ЭВМ ситуация охватывает диа-
пазон от несколько менее удобной до почти невыносимой. Обслуживание устройств в
ЭВМ PDP-11 проводится относительно легко из-за того, что регистры устройств имеют
адреса, и, таким образом, ими можно манипулировать с помощью стандартных инструк-
ций ЭВМ PDP-11. Но за это удобство приходится расплачиваться изрядным объемом ад-
ресного пространства, которое иначе могло бы использоваться под оперативную память.
12.10. УПРАЖНЕНИЯ
12.3.1. Чем отличается действие ЦП при прерывании и при выполнении инструкции
TRAP?
12.3.2. Напишите подпрограмму обработки прерываний от часов, которая корректи-
рует реальное время, как в примере из разд. 12.3, но делает это более изощренно и пред-
почтительно меньшим числом программных инструкций.
12.3.3. Следующая подпрограмма обслуживания устройства просто ведет в двух ячей-
ках, COUNT и COUNT + 1, подсчет прерываний от устройства. Здесь потенциально могут
возникать ошибки программирования. Почему? Найдите два разных способа, как мож-
но избежать этих потенциальных проблем.
DEV : MOV #COUNT, R2
INCB(R2)+
INCB(R2)
RTI
12.4.1. Кило (К) — это приставка, означающая 1 000, но в отношении ЭВМ мы обычно
имеем в виду, что кило означает 10241О, степень двойки, близкую к 1000. Таким обра-
зом, о 1024 словах мы говорим как о 1 килослове (1 Келов), а о 1024 байтах - как об
одном килобайте (1 Кбайт). Если дана схема адресации, обсужденная в разд. 12.4, то
сколько Келов памяти отводится для пользователя и сколько резервируется для регист-
ров устройств, т. е. под адреса на шинах, а не в оперативной памяти?
12.4.2. На рис. 12.4.1 имеется символьное обозначение, показанное на рис. 12.10.1.
Как можно интерпретировать этот трехвходовый вентиль И? То есть, каким будет его
выход при заданных входах А, В и С?
12.5.1. В разд. 12.5 мы видели, что сетевые часы можно использовать для ведения ре-
ального времени (в режиме с прерываниями) или как интервальный таймер (в режиме
без прерываний). Мы не могли использовать часы как хранитель времени и затем, когда
возникла бы необходимость в интервальном таймере, временно запрещать прерывания
от часов для этой цели без потери реального времени. Одно решение этой дилеммы со-
стоит в использовании двух часов — для каждой цели, и на некоторых вычислительных
системах именно так и поступают. Другое решение состоит в написании программного
Рис. 12.10.1
267
000320 000364 DEVA *,320 DEVB = 364 t
001000 000230 STARTs SPL 0
001002 012767 002240’ 000320 MOV OINTA'DEVA
001010 012767 000200 000322 MQV »200'DEVA+2
001016 012767 002406’ 000364 MOV tlMTB'DEVB
001024 012767 000240 000366 MOV *240,DEVB+2
001032 005001 CLR Rl < (ПРОИЗОШЛО ПРЕРЫВАНИЕ ОТ УСТРОЙСТВА А)
001034 001036 005201 000000 INC Rl HALT
002240 005201 INTA: INC Rl
002242 000002 ( RTI (ПРОИЗОШЛО ПРЕРЫВАНИЕ ОТ УСТРОЙСТВА В>
002406 005201 INTB: INC Rl
002410 000002 RTI
Рис. 12.10.2
сегмента, который использует часы, ведущие время дня в качестве таймера, например
для выполнения задержки на 10 с без запрещения способности генерировать прерывания
и, таким образом, без потери реального времени. Здесь может оказаться полезной ин-
струкция WAIT (см. приложение А).
12.8.1. a) SPL 0 устанавливает уровень приоритета ЦП равным 0, и то же самое делает
CLR PSW. Каковы (если они есть) различия между этими инструкциями?
б) Каково различие между инструкциями SPL 6 и MOV #300, PSW?
в) Почему инструкция BIS #300, PSW не обязательно установит уровень приоритета
ЦП равным 6?
12.8.2. а) Рассмотрим два устройства, А и В, с векторами прерываний DEVA и DEVB,
генерирующие прерывания соответственно на уровнях 4 И 5. Дайте подробные объясне-
ния, каким при выполнении программного сегмента на рис. 12.10.2 будет содержимое
стека каждый раз, когда меняется состояние стека. Предполагается, что первоначально
стек пустой.
б) Задача та же, что и в п. а), но теперь предположите, что устройства А и В генериру-
ют прерывания соответственно на уровнях 5 и 4. В самой программе не предполагается
никаких изменений.
12.8.3. Мы видели, что ЭВМ PDP-11 обладает хорошо проработанной структурой
прерываний. Рассмотрим теперь ЭВМ, намного менее изощренную в этом отношении.
Мы полагаем, что ее внешние устройства имеют статусные регистры, так что можно
определять состояние устройства. Однако когда прерывания поступают от любого уст-
ройства, управление всегда проходит через вектор к одной и той же подпрограмме об-
служивания. (Некоторые популярные микро-ЭВМ имеют эту простую структуру.)
а) Если управление всегда передается к некоторой общей подпрограмме обслужива-
ния, то как эта подпрограмма может определить, какое устройство или устройства тре-
буют обслуживания?
268
б) Предположим, что ответ на вопрос из п. а) получен. Объясните, как может общая
подпрограмма обслуживания передать управление подпрограмме, которая обслужит за-
тем прерывающее устройство?
в) Предположим, что получены удовлетворительные ответы на вопросы из а) и б).
Объясните, как можно наложить на эти устройства приоритетную схему? Это ни в коей
мере не тривиальная задача, но ее можно решить программным способом. {Указание.
Общая подпрограмма обслуживания может следить за тем, подпрограмма обслужива-
ния какого устройства выполняется в настоящее время, если в данный момент вообще
что-либо выполняется.) Эту ситуацию можно частично эмулировать на ЭВМ PDP-11 сле-
дующим образом. Рассмотрим три устройства, причем все они генерируют прерывания
на уровне 4. Поместите адрес общей подпрограммы обслуживания в первое слово век-
тора прерывания для каждого устройства.
12.9.1. Объясните, как клавиатура может обслуживаться без прерываний на основе
опроса устройства, т. е. путем постоянной проверки статусного регистра клавиатуры.
При каких разумных условиях опрос мог бы прекращаться?
12.9.2. Первый пример из разд. 12.9 обладал той особенностью, что возврат каретки
обнаруживался как специальный случай и программа вставляла перевод строки, чтобы
обеспечить продвижение бумаги. Добавьте программные инструкции, достаточные для
того, чтобы подпрограмма обслуживания могла обрабатывать один или несколько зна-
ков стирания RUBOUT (или DELETE) (код ASCII - 177).
12.9.3. Отыщите описание концепции счетчика забивки терминала1 и соответственно
подстройте две программы из разд. 12.9.
12.9.4. Хотя под код ASCII знака требуется только 7 бит, некоторые терминальные
контроллеры всегда выставляют бит 7 в регистре данных. Таким образом, например,
код знака А появится в регистре данных в виде 301, а не 101, как следовало бы ожи-
дать. Как с этим можно справиться, независимо от того, устанавливает контроллер бит
7 или нет?
12.9.5. Пересылка восьмибитовых данных между терминалом и регистром данных
его контроллера может происходить двумя способами. При одном, называемом парал-
лельной передачей, 8 бит данных непосредственно переносятся в регистр данных через
восемь линий, по одной на каждый бит. При другом, называемом последовательной пе-
редачей, байт в одном устройстве разбирается на отдельные биты, и каждый бит переда-
ется к другому устройству, где они собираются в восьмибитовый байт, что соответствен-
но называют сериализацией и десериализацией. Найдите соответствующую литературу
и определите значения следующих терминов:
а) (токовая) посылка; б) пауза; в) стартовый бит; г) стоповый бит; д) четность;
е) скорость передачи в бодах.
12.9.6. Если в конфигурацию ЭВМ PDP-11 входит АЦПУ в качестве высокоскорост-
ного устройства вывода, то этим АЦПУ должно заниматься системное программное
обеспечение. По соответствующему руководству фирмы-производителя познакомьтесь
с подробностями работы АЦПУ и напишите обслуживающие подпрограммы для работы
с этим устройством при использовании прерываний и без прерываний.
12.9.7. В свете нашего опыта по аппаратным прерываниям ЭВМ пересмотрите сцены
из реальной жизни в разд. 12.2. В частности, скажите, какие аппаратные концепции во-
влечены в каждую из этих сцен — прерывания, помещение в стек c(PSW) и с (PC), прио-
ритеты и т. п.
12.9.8. Напишите программу, которая распечатывает сообщение, содержащее 10 зна-
ков или около того, на консольном печатающем устройстве при использовании преры-
ваний. Часы также порождают прерывания, и, как только начинается распечатка, произ-
водится наблюдение времени часов. После окончания распечатки опять наблюдается вре-
мя часов и прошедшее время распечатывается (в каких-то подходящих единицах).
1 Имеется в Виду выдача на терминал ’’пустых” знаков для компенсации инерцион-
ности его механизмов. — Прим, перев.
269
Г Л А В A 13. МАКРОИНСТРУКЦИИ И УСЛОВНОЕ АССЕМБЛИРОВАНИЕ
13.1. ВВЕДЕНИЕ
Рассмотрим следующую простую проблему. Предположим, что у нас есть
строка знаков ASCII кода, размещенная в блоке соседних байтов, первый из
которых имеет символьный адрес TEXT. Мы хотим найти место (адрес) пер-
вого появления в строке какого-то определенного знака, например X. Чита-
телю не составит труда установить, что программный сегмент на рис. 13.1.1
выполнит эту работу.
Подобная задача ни в коей мере не является необычной, поскольку про-
граммам, требующим информации от пользователя, может понадобиться ска-
нировать вводимый пользователем текст в поисках каких-то ключевых зна-
ков, позволяющих интерпретировать ответ. Действительно, вполне вероят-
но, что такое сканирование встречается достаточно часто, поэтому мы вряд
ли станем кодировать этот программный сегмент непосредственно в ходе
программы, как это показано на рис. 13.1.1. Скорее, мы напишем для выпол-
нения этой работы короткую подпрограмму. Пример такой подпрограммы
дан на рис. 13.1.2; мы полагаем, что код ASCII знака, который предстоит
найти, помещается в R0, а регистром для перехода к подпрограмме является
PC. Мы сделали также следующее улучшение по сравнению с программным
сегментом, показанным на рис. 13.1.1. Полагая, что текстовая строка окан-
чивается знаком NUL (код ASCII = ООО), мы можем определить ошибку, ко-
торая произойдет, если заданный знак в строке не присутствует. В таком слу-
чае мы устанавливаем бит переноса в PSW. Если знак в строке обнаруживает-
ся, то мы очищаем бит переноса. Таким образом, после возврата из подпро-
граммы основная часть программы может выполнить инструкцию
BCS ERROR
для обработки того факта, что ожидаемый знак в строке не был найден. (У ста-
новка или очистка бита переноса — это наиболее удобный и эффективный
способ передачи информации типа ”да—нет” между программными модуля-
ми. Если мы проявляем достаточное внимание, то можем использовать ана-
логично также и другие биты кодов условий в PSW.)
Предположим затем, что мы имеем дело с программой, в которой часто
необходимо проталкивать в стек содержимое трех регистров, к примеру R1,
R3 и R5, для его сохранения, чтобы можно было использовать эти регистры
временно для других целей. (Предположительно мы захотим также восста-
новить их содержимое после того, как они больше не будут нужны.) Опять,
поскольку нам не хочется включать эти три проталкивания непосредственно
в линейный ход программы всякий раз, когда в этом возникает нужда, мы
можем решить написать подпрограмму, такую, как показано на рис. 13.1.3.
Но небольшое размышление показывает, что эта подпрограмма не выполнит
нужную нам работу. Хотя инструкция RTS PC вытолкнет верхний элемент
стека в PC, на вершине стека в этот момент будет c(R5) , а не значение PC в
момент, когда выполнялась соответствующая инструкция JSR PC, SAVE.
(В действительности можно спасти положение и в упражнениях читатель най-
дет соответствующие предложения.) Таким образом, представляется, что у
\ HOVB
100РД СМР8
ВНЕ
DEC
•T£XT»R1
RO.(R1»♦
LOOP
Rl
;ЗАНЕСТИ КОД ЗНАКА В R0
;АДРЕС ТЕКСТА В R1
(ЗНАК НАИДЕН'-
(НЕТ -- ПРОДОЙПАТЬ ПОИСК
(ДА — ПОДСТРОИТЬ УКАЗАТЕЛЬ
: (ПРОДОЛПЕНИЕ
; ПРОГРАММЫ)
Рис. 13.1.1
GETCHR: MOV «TEXT,R1 (АДРЕС ТЕКСТА В Rl
LOOP: TSTB (Rl) (ПРОВЕРКА НА КОНЕЦ ТЕКСТА
BEQ EOT (КОНЕМ ТЕКСТА — ОШИБКА
CMPB R0URD + (ЗНАК НАЙДЕН?
BNF LOOP (НЕТ — ПОВТОРИТЬ ПОИСК
DEC Rl ;Да — подстроить указатель
CLC (УСТАНОВИТЬ ФЛАГ ’ОШИБКИ НЕТ*
BR DONE ( И ПРОПУСТИТЬ
EOT: SEC (ОШИБКА — УСТАНОВИТЬ ФЛАГ
DONE: RTS PC (ВОЗВРАТ
Рис. 13.1.2
SAVE: MOV R1»-(SP) (СОХРАНИТЬ
MOV R3,-(SP> ; ТРИ
MOV R5»-(SP> ( РЕГИСТРА
RTS PC ( и вернуться
Рис. 13.1.3
нас нет иного выбора, как кодировать эти три инструкции проталкивания не-
посредственно в ходе программы всякий раз, когда они потребуются.
13.2. МАКРОИНСТРУКЦИИ
Мы, вероятно, быстро устанем, когда нужно будет раз за разом вставлять
в программу одни и те же инструкции. Поэтому можно рассмотреть вопрос
о передаче этой утомительной работы ассемблирующему служащему (понят-
но, наш служащий давно уже заменен ассемблером ЭВМ PDP-11, но на какое-
то время будет полезно вернуть его на сцену). Таким образом, передавая
ему программу для ассемблирования, мы можем сопроводить ее следующим
замечанием: ”В этой программе мы делаем многочисленные ссылки на тер-
мин SAVE, который не является ни мнемоникой инструкции, ни директивой
для Вас. Каждый раз, когда Вы увидите слово SAVE, вставьте в программу
следующие инструкции”:
MOV Rl,—(SP)
MOV R3,-(SP)
MOV R5,-(SP)
He видно причин, почему бы служащий не справился с этой работой, хотя он
должен быть внимательным, чтобы избежать нескольких проблем, которые
мы будем вскоре обсуждать.
Здесь мы создали с помощью термина SAVE и наших указаний ассембли-
рующему служащему некий тип ’’суперинструкции” — пакета инструкций
ЭВМ PDP-11, которому было присвоено имя, в частности, имя SAVE. Такую
суперинструкцию называют макроинструкцией1. Набор инструкций, кото-
1 В литературе используется также термин ’’макрос”. - Прим, перев.
271
270
рые составляют макроинструкцию, называется определением макроииструк-
ции или, для краткости, макроопределением. Таким обрром, в данном слу-
чае имя макроинструкции есть SAVE, а ее определений задается тремя ин-
струкциями MOV, показанными выше. Когда служащий заменяет макроин-
струкцию ее определением, т. е. заменяет ссылку на SAVE тремя инструкци-
ями MOV и помещает эти инструкции в исходный код, мы говорим, что он
расширяет макроинструкцию. И, наконец, когда программист ссылается на
макроинструкцию SAVE в исходной программе, то мы говорим, что он (или
она) вызывает макроинструкцию или обращается к ней.
Поскольку макроинструкция SAVE может вызываться в программе мно-
го раз, хотя наш служащий и расширяет ее, мы можем предпочесть не видеть
расширенного кода в листинге исходной программы. В других случаях мо-
жет оказаться полезным, чтобы расширения были видны. Следовательно, нам
нужны какие-то средства, чтобы можно было дать указание служащему по-
казать эти расширения или подавлять такой листинг. Например, если исход-
ный код
SAVE
INC R2
был ассемблирован, например, начиная с ячейки 002404, и мы решили уви-
деть листинг макрорасширений, служащий должен написать следующий код:
002404 SAVE
002404 010146 MOV R1,-(SP)
002406 010346 MOV R3,-(SP)
002410 010546 MOV R5,-(SP)
002412 005202 INC R2
Но если бы мы указали, что эти расширения в листинге присутствовать не
должны, то увидели бы:
002404 SAVE
002412 005202 INC R2
Конечно, машинный код для трех инструкций MOV служащий все же поме-
стил бы в объектный файл в ячейках 002404, 002406 и 002410, независимо
от того, заносил ли он их фактически в листинг или нет.
Процесс, через который проходит служащий, расширяя эту макроинструк-
цию, весьма простой, но имеет смысл рассмотреть его более подробно. Вспо-
минаем, что при ассемблировании программы служащий (или ассемблер) де-
лает два прохода по исходному коду. На первом из них он определяет адреса
различных инструкций с помощью счетчика распределения ячеек и устанав-
ливает значения для символьных адресов, которые ему попадаются. Когда на
этом первом проходе он встречает псевдомнемонику SAVE, то понимает, что
она может быть ссылкой на какую-то специальную макроинструкщпо, опре-
деленную программистом. Тогда он просматривает текущие макроопределе-
ния — в программе может быть больше, чем одно такое определение, — и на-
ходит макро инструкцию по имени SAVE. Он переписывает определение в
том виде, как оно задано, не обращая никакого внимания на смысл того,
что он записывает. Наконец, он продолжает процесс ассемблирования на пер-
вом проходе, просматривая строки исходного кода, который он только что
272
записал на месте SAVE, назначает адреса инструкциям и значения символам
ит.п. После этого на втором проходе он завершает процесс трансляции.
13.3. АССЕМБЛЕР МАКРОИНСТРУКЦИЙ ЭВМ PDP-11
Большинство поставляемых фирмами-изготовителями ассемблеров могут
обрабатывать макроинструкции точно так же, как наш служащий, и ассемб-
лер ЭВМ PDP-11 не является исключением. На рис. 13.3.1 показан ассемблер-
ный листинг программного сегмента, в котором макроинструкция SAVE
определяется и затем вызывается. Строка 1, .LIST ME — это директива ас-
семблера, которую мы использовали, чтобы запросить листинг макрорасши-
рений (LISTing of the Macro Expansion). Обратите внимание, мы здесь гово-
рим не о макроопределении, которое в листинге также будет присутствовать,
но о показе в листинге инструкций, которые составляют макроинструкцию,
всякий раз, когда эта макроинструкция вызывается. В строке 3 директива
•MACRO информирует ассемблер о том, что дальше следует макроопределе-
ние и что имя макроинструкции есть SAVE. Строки 4, 5 и 6 — это, конечно,
определение макроинструкции SAVE. Строка 7, JENDM — это другая дирек-
тива ассемблера, которая на этот раз информирует ассемблер о том, что он
достиг конца макроопределения (the END of the Macro definition). Мы ви-
дим, что ни адресов, ни объектного кода этим инструкциям назначено не бы-
ло, чего и следовало ожидать, поскольку строки от 3-й до 7-й — это,в конце
концов, просто определение макроинструкции, и эта макроинструкция еще
ни разу не вызывалась. На это определение полезно смотреть как на справоч-
ную формулировку, которую ассемблер просто помещает в таблицу опреде-
лений макроинструкций под именем SAVE. В строке 9 эта макроинструкция
действительно вызывается. В листинге показаны три строки исходного кода
вместе с их адресами и объектным кодом и комментариями. Эти строки по-
явились в результате директивы .LIST ME в строке 1. Если бы вместо нее мы
использовали директиву .NLIST ME (do Not LIST Macro Expansion — не по-
казывать в листинге макрорасширения) , то эти три строки не были бы пока-
заны, хотя, конечно, соответствующий машинный код был бы помещен в
объектный файл.
1 2 .LIST ! ME
3 .MACRO SAVE (МАКРООПРЕДЕЛЕНИЕ
4 MOV R1»-<SP> ;СОХРАНИТЬ
5 MOV R3»-<SP) * ТРИ
6 MOV R5»-(SP) • РЕГИСТРА
7 .ENDM (КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
8 (
? 000000 START) SAVE (ВЫЗВАТЬ МАКРОС
оооооо 010146 MOV R1»-(SP> ;СОХРАНИТЬ
000002 010346 NOV R3»-(SP) ! ТРИ
000004 010546 MOV R5.-CSP) 1 РЕГИСТРА
10 000006 ;(НР0Д0Л1ЕНИЕ
; ПРОГРАММЫ)
Рис. 13.3.1
273
13.4. НЕСКОЛЬКО СЛОВ В КАЧЕСТВЕ ПРЕДОСТЕРЕЖЕНИЯ f
На рис. 13.4.1 представлен листинг программного сегмента, в котором ма-
кроинструкция SAVE определяется после того, как она вызывается в пер-
вый раз. По-видимому, все в порядке. Хотя сама макроинструкция опреде-
ляется только в строках от 5-й до 9-й, из-за ссылки на SAVE в строке 3 ас-
семблер вставил три инструкции MOV и правильный машинный код для них
в ожидаемые ячейки — 000000, 000002 и 000004. Ассемблер также правиль-
но поместил инструкцию CLR R4 в ячейку 000006. Из этого примера можно
было бы заключить, что несущественно, где в исходной программе определя-
ется макроинструкция. Но если хотя бы немного подумать о том процессе,
с помощью которого ассемблер (или ассемблирующий служащий) должен
обрабатывать вызов макроинструкции, то приходится сделать совершенно
противоположное заключение — ассемблер эту ситуацию правильно не обра-
ботает. Чтобы разрешить эту дилемму и увидеть, что ассемблер здесь факти-
чески сделал, модифицируем этот программный сегмент, просто поместив
метку ADDR на инструкцию CLR R4 (рис. 13.4.2).
Опять кажется, что вызов макроинструкции в строке 3 обработан пра-
вильно, но здесь есть три особенности. Во-первых, строка 11, в которой при-
сутствует метка ADDR, помечена флагом Р. Это обозначает фазовую ошибку
(Phase error), и с этим термином нам вскоре придется иметь дело. Затем об-
ратите внимание, что в листинге программы метка ADDR имеет значение
000006, тогда как в таблице символов ее значение дано как 000002. Нако-
нец, в таблице символов содержится символ SAVE, который, очевидно, не
определен, что показано шестью звездочками в поле значения. Все эти три
аномалии будут разъяснены, если внимательно просмотреть действия ассемб-
лера на первом и втором проходах.
На своем первом проходе по исходному коду ассемблер встречает (в стро-
ке 3) слово SAVE. Он понимает, что это не мнемоника ассемблера PDP-11,
а также и не его директива. Просматривая таблицу определений макроин-
струкций, он обнаруживает, что такого определения нет, и, следовательно,
заключает, что SAVE — это не имя макроинструкции. Единственный способ,
каким ассемблер может разрешить эту ссылку на SAVE, состоит в том, что-
бы предположить, что SAVE должно быть символьным адресом, который
1 2 .LIST 1 ME
3 000000 START1 SAVE 'ВЫЗВАТЬ МАКРОС
000000 010146 MOV R1,-<SP) 'СОХРАНИТЬ
000002 010346 MOV R3»-(SP> '-TPM
000004 010546 MOV R5»-(SP> ? РЕГИСТРА
4
5 .MACRO SAVE 'МАКРООПРЕДЕЛЕНИЕ
6 MOV Rb-(SP> 'СОХРАНИТЬ
7 MOV R3»-(6P> » ТРИ
8 MOV R5»-(SP) ' РЕГИСТРА
9 -ENDM 'КОНЕК МАКРООПРЕДЕЛЕНИЯ
10
11 000006 005004 CLR R4 'ОЧИСТИТЬ РЕГИСТР
12 000010 1(ПРОДОЛЖЕНИЕ
' ПРОГРАММЫ)
Рис. 13.4.1
274
1 •LIST ME
2 1
3 000000 STARTs SAVE ;ВЫЗВАТЬ МАКРОС
000000 010146 MOV Ri»-(SP> .‘СОХРАНИТЬ
000002 010346 MOV R3»-(SP) S ТРИ
000004 010546 MOV R5»-(SP> S РЕГИСТРА
4 1
5 MACRO SAVE !МАКРООПРЕДЕЛЕНИЕ
6 MOV Rlr-CSP) ;СОХРАНИТЬ
7 MOV R3»-(SP> S ТРИ
8 MOV R5»-(SP> S РЕГИСТРА
9 .ENDM ;КОНЕЙ МАКРООПРЕДЕЛЕНИЯ
10
Р 11 000006 005004 ADDR: CLR R4 ОЧИСТИТЬ РЕГИСТР
12 0000Ю ;(ПРОДОЛЖЕНИЕ
; ПРОГРАММЫ)
ТАБЛИЦА СИМВОЛОВ
ADDR 000002R SAVE = *•••»• START OOOOOOR
Рис. 13.4.2
еще не встречался, т. е. ссылкой вперед и что намерением программиста бы-
ло использовать умолчание
.WORD SAVE
Таким образом, ассемблер помещает SAVE в свою таблицу символов с нота-
цией ♦***♦*, означающей, что значение SAVE еще неизвестно. Поскольку
SAVE фактически в этом программном сегменте никогда не определяется,
нам теперь понятно его появление в таблице символов со ’’значением”
Полагая, что здесь имелось в виду использовать директиву .WORD, ассемб-
лер увеличивает свой счетчик распределения ячеек до 000002, чтобы отвести
место под все еще неизвестное слово, и продолжает сканирование строк ис-
ходного кода.
Затем в строке 5 ассемблер встречает определение макроинструкции
SAVE. Ассемблер сохраняет исходный код между строками 5 и 9 для обра-
щения к нему в будущем и затем видит строку
ADDR : CLR R4
Здесь он встречает определение символа ADDR. Он помещает этот символ в
свою таблицу символов и присваивает ему значение, соответствующее теку-
щему значению счетчика распределения ячеек, а именно 000002. Этим и
объясняется то значение, которое показано в таблице символов. Теперь ас-
семблер завершает свой первый проход по оставшейся исходной программе.
Когда ассемблер начинает свой второй проход по исходному коду, он
опять встречает слово SAVE. Здесь особое внимание следует уделить тому,
в каком порядке ассемблер пытается разрешить эту ссылку. Он опять пони-
мает, что это и не мнемоника, и не директива, и мы можем подумать, что он
немедленно обратится к своей таблице символов за значением SAVE. Одна-
ко когда ассемблер встречает конструкцию, которая может быть или сим-
вольной ссылкой, или вызовом макроинструкции, он сначала производит
поиск в своей таблице макроопределений. Поступая так, он теперь находит
определение макроинструкции SAVE. Он вставляет код для макроинструк-
ции и затем обрабатывает его, т. е. ассемблер генерирует машинный код для
каждой из трех инструкций MOV, не забывая для каждой из них продвигать
свой счетчик распределения ячеек на 2. Таким образом, после того, как ас-
275
1 2 з .LIST .MACRO ME SAVE / ;МАКРООПРЕДЕЛЕНИЕ
4 MOV Rb-(SP) 1СОХРАНИТЬ
5 MOV R3,-(SP) 1 ТРИ
4 MOV R5,-(SP) ; РЕГИСТРА
7 .ENDM ;КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
8 9 000000 SAVE: . BLKW 20 ;ЧИСЛА, ПОДЛЕХАКИЕ СОРТИРОВКЕ
10 •
11 000040 START: MIN.DEC «SAVE,420 ?ЧИТАТЬ 16 ЧИСЕЛ
12 000074 SAVE !ВЫЗВАТЬ МАКРОС
000074 010146 MOV R1»-<SP) tСОХРАНИТЬ
000076 010346 MOV R3,-(SP) ! ТРИ
ОООЮО 010546 MOV R5,-(SP) ; РЕГИСТРА
13 000102 004567 JSR R5.S0RT ОТСОРТИРОВАТЬ ЧИСЛА
000210
14 000106 SAVE
000106 010146 MOV R1,-(SP) •СОХРАНИТЬ
000110 010346 MOV R3,-(SP) 1 ТРИ
000112 010546 MOV R5»-(SP) ; РЕГИСТРА
15 000114 000020 20 ; ПОДПРОГРАММЫ
1А 000116 ;(ПРОДОЛЖЕНИЕ
; ПРОГРАММЫ)
Рис. 13.4.3
семблер завершил обработку макроинструкции SAVE, его счетчик распреде-
ления ячеек имеет значение 000006, и ассемблер готов ассемблировать сле-
дующую строку исходного кода, а именно инструкцию INC R4, помеченную
меткой ADDR. В этом месте ассемблер выполняет небольшую проверку и об-
наруживает проблему, поскольку его счетчик распределения ячеек равен
000006, тогда как он должен бы иметь значение символа ADDR. Но в таблице
символов указано значение 000002. Каким-то образом (и мы теперь, конеч-
но, знаем почему) значение, присвоенное символу ADDR между двумя
проходами ассемблера, выпало из фазы. Поскольку исправить эту ситуацию
нет никакой надежды, ассемблер помечает эту строку флагом фазовой
ошибки.
Очевидно, что описанная выше трудность является результатом того, что
мы вызываем макро инструкцию прежде ее определения. Это почти всегда
приводит к проблемам. Мы предостерегаем читателя, что он должен заботить-
ся о том, чтобы макроинструкции определялись додого,как они вызывают-
ся. Многие программисты стремятся размещать все определения макроин-
струкций в самом начале исходной программы, чтобы к ним легко было об-
ращаться (программисту, а не ассемблеру), и хотя это считается хорошей
программистской практикой, она необязательна — пока ассемблер встречает
определение макроинструкции до любой ссылки на нее, все отлично.
В качестве второго примера неправильного хода дел рассмотрим програм-
му, которая использует одно и то же имя для некоторого символа и для не-
которой макроинструкции. Программа на рис. 13.4.3 иллюстрирует эту проб-
лему. Она считывает 16ю чисел (в десятичном виде) и затем вызывает под-
программу, названную SORT, для размещения этих чисел в каком-то (не ука-
занном здесь) порядке. Подпрограмме SORT необходимо знать адрес перво-
го числа и счетчик чисел, и эти два параметра передаются подпрограмме с
помощью директив .WORD, непосредственно следующих за обращением к
подпрограмме. Здесь мы делаем два неудачных выбора. Во-первых, имя, ко-
торое мы даем блоку памяти для хранения чисел (SAVE) является также
именем макроинстрУкции, определенной в программе. Во-вторых, мы пола-
276
1 .LIST ME
2 г
3 .MACRO SAVE {МАКРООПРЕДЕЛЕНИЕ
4 MOV R1»-(SP) {СОХРАНИТЬ
5 MOV R3»-(SP) ; ТРИ
6 MOV R5»-(SP) { РЕГИСТРА
7 .ENDM ;КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
9
9 000000 SAVE: .BLKW 20 ! ЧИСЛА» НОДЛЕПАЯИЕ СОРТИРОВКЕ
10
11 000040 STARTs MIN.DEC «SAVE»»20 ;ЧИТАТЬ 16 ЧИСЕЛ
12 000074 SAVE {ВЫЗВАТЬ МАКРОС
000074 010146 MOV R1,-(SP) {СОХРАНИТЬ
000076 010346 MOV R3»-<SP) { ТРИ
000100 010546 MOV R5»-<SP) 1 РЕГИСТРА
13 000102 004567 JSR R5»S0RT {ОТСОРТИРОВАТЬ ЧИСЛА
000204
14 000106 000000* .WORD SAVE {ПАРАМЕТРЫ ДЛЯ
15 000110 000020 .WORD 20 { ПОИРОГРАИМЫ
16 000112 {(НР0Д0Л1ЕНИЕ
{ ПРОГРАММЫ)
Рис. 13.4.4
гаемся на то, что ассемблер использует для SAVE по умолчанию .WORD
SAVE. Строка 12 в листинге — это сознательный вызов макроинструкции
SAVE; т. е. в этом месте мы хотим сохранить содержимое трех регистров.
Исходный код в строках 13,14,15 выглядит так:
JSR R5,SORT
SAVE
20
Здесь мы, конечно, хотели, чтобы SAVE и 20 были параметрами директив
.WORD, необходимыми подпрограмме SORT. Однако ассемблер, встретив
SAVE (без директивы .WORD), произвел расширение макроинструкции
SAVE, поскольку он опять встретил конструкцию, которая может быть вы-
зовом макроинструкции, и, следовательно, перед поиском в своей таблице
символов он просмотрел таблицу макроопределений. Теперь ясно, что в про-
грамме создались серьезные трудности.
Этой проблемы можно было бы избежать, если бы мы явным образом ис-
пользовали .WORD SAVE вместо SAVE. Поскольку .WORD SAVE — это та-
кая конструкция, которая не может быть вызовом макроинструкции, ас-
семблер сразу же произвел бы поиск в своей таблице символов значения
SAVE. Как видно из рис. 13.4.4, использование директив .WORD действитель-
но выводит нас из затруднения. Но мы еще раз предостерегаем читателя —
программист, использующий одно и то же имя для символа и макроинструк-
ции, очень вероятно, столкнется с тем, чего заслуживает.
135. ПЕРЕДАЧА АРГУМЕНТОВ МАКРОИНСГРУКЦИИ
Макроинструкция SAVE из предыдущих четырех разделов оправдывала
свое предназначение, но один из ее недостатков состоит в том, что хотя она
сохраняет в стеке содержимое трех регистров, она может использоваться для
сохранения содержимого только указанных регистров: Rl, R3 и R5. Если по
ходу программ нам необходимо сохранить с(Rl) , c(R2) и c(R4), то для вы-
полнения этого макроинструкцию SAVE мы использовать не можем. Мы ли-
бо должны будем кодировать соответствующие инструкции прямо в про-
грамме, либо, возможно, напишем вторую макроинструкцию для этой но-
вой ситуации.
277
Очевидно, что нам необходимо некоторое изменяемое определение макро-
инструкции, которое сохраняет содержимое неуказанных регистров. Регист-
ры же, содержимое которых подлежит сохранению, мы могли бы указывать
каждый раз, когда вызывается эта макроинструкция. То есть нам нужно
определение наподобие следующего:
MOV RA, — (SP)
MOV RB, —(SP)
MOV RC, — (SP)
в котором мы придаем буквам А, В и С конкретные значения, например
А = 1, В = 2, С = 4, при каждом вызове макроинструкции. Необходимо ин-
формировать нашего служащего (или ассемблер), что буквы А, В и С - это
формальные аргументы, которые будут заменяться фактическими аргумен-
тами, задаваемыми при каждом вызове макроинструкции. Это довольно
легко можно выполнить в операторе ’’поименования” макро инструкции.
Например, строка
.MACRO SAVE А, В, С
скажет ассемблеру, что макроинструкция, определение которой следует даль-
ше, имеет имя SAVE и что А, В и С — это формальные аргументы, которые
будут заменяться фактическими аргументами при вызове макроинструкции.
Модифицированное определение макроинструкции теперь выглядит так:
.MACRO SAVE А, В, С
MOV RA, — (SP)
MOV RB, — (SP)
MOV RC, —(SP)
.ENDM
Теперь при вызове макроинструкции, например оператором SAVE 1,2,4, мы
ожидаем, что ассемблер расширит ее так, как определено выше, и затем вы-
полнит указанные подстановки, так что окончательный продукт будет таким:
MOV Rl,—(SP)
MOVR2, — (SP)
MOV R4, — (SP)
Как должен ассемблер обрабатывать вызовы макроинструкции наподобие
SAVE 1, 2,4? Оператор поименования в макроопределении
.MACRO SAVE А, В, С
называется оператором прототипа макроинструкции; он служит нескольким
целям. Во-первых, он,,конечно, поименовывает макроинструкцию. Во-вто-
рых, в нем подразумевается, что каждый раз, когда макроинструкция вызы-
вается, после ее имени будут даны три фактических аргумента, разделенных
запятыми (в действительности разделителями могут быть также и пробе-
лы). Наконец, он показывает с помощью имен формальных аргументов А,
В и С, какие буквы в определении макроинструкции должны заменяться
фактическими аргументами. Обратите внимание, что подстановка аргумен-
тов зависит от позиции — SAVE 1,2, 4 присвоит А значение 1, В значение 2,
а С значение 4, тогда как SAVE 2,1,4 даст А = 2, В = 1 и С = 4. Зная эти осно-
вы, мы готовы определить и вызывать эту макроинструкцию. Как видно из
листинга на рис. 13.5.1, результаты далеки от удовлетворительных.
1 .LIST ME
2 I
3 .MACRO SAVE A.B.C ИЮКРООИРЕВЕЖНИЕ
4 MW RA,-(SP) ;СОХРАНИТЬ
5 MOV RB,-<SP) ; TPM
6 MOV RC.-(SP) 1 РЕГИСТРА
7 .ENDM ;K0HE4 МАКРООПРЕДЕЛЕНИЯ
8
9 000000 START: SAVE 1,2.4 JВЫЗВАТЬ МАКРОС
U 000000 014746 MOV RA.-(SP) СОХРАНИТЬ
000000
U 000004 016746 NOV RB»-<SP) 1 ТРИ
000000
U 000010 016746 MOV RC.-1SP) ? РЕГИСТРА
000000
10 000014 >(йРОаОЛХЕНИЕ
! ПРОГРАММЫ»
Рис. 13.5.1
Флаги U с левого края показывают, что некоторые символы являются не-
определенными. Чтобы понять, почему это так, проследам более детально
действия ассемблера, направленные на расширение этой макроинструкции.
Очевидно, что ассемблер не испытывает никаких затруднений по поводу опе-
ратора прототипа макроинструкции в строке 3 или определения ее в строках
от 4-й до 6-й. Когда макроинструкция вызывается в строке 9, ассемблер
предпринимает следующие действия: он просматривает свою таблицу макро-
определений, находит определение SAVE и расширяет его следующим обра-
зом:
MOV RA, — (SP)
MOV RB, -(SP)
MOV RC, — (SP)
Поскольку оператор прототипа макроинструкции указывает три формаль-
ных аргумента и поскольку в макровызове также указаны три фактических
аргумента, ассемблер присваивает значения: А= 1,В = 2иС = 4. Теперь ас-
семблер проходит по только что сгенерированному исходному коду в готов-
ности произвести эти подстановки для А, В и С, т. е. он должен заменить каж-
дое А на 1, каждое В на 2 и каждое С на 4. Однако он не находит ни одного А.
Конечно, он увидит терм RA, но букву А, являющуюся его частью, он как
вхождение А не рассматривает. Вместо этого он воспринимает RA как некий
неделимый терм и никакой подстановки не производит. Аналогично он не де-
лает подстановок в RB и RC. Теперь ясно, почему эти символы были отмече-
ны флагами как неопределенные — ассемблер оценивает RA, RB и RC как
символы программы, но определений для них он найти не может.
Наше разочарование по поводу неспособности ассемблера справиться с
тем, что представляется простой задачей, должно смягчиться даже с некото-
рой степенью благодарности. Ибо предположим, что ассемблер справился с
этим расширением макроинструкции, как мы хотели. Чтобы понять, какими
бедственными могут быть последствия, рассмотрим следующую простую
макроинструкцию, которая просто выталкивает верхний элемент стека в
заданный регистр (конечно, такую однострочную макроинструкцию никто не
стал бы писать в виде макроинструкции, а закодировал бы прямо в про-
грамме) .
.MACRO POP Р
MOV (SP)+,RP
.ENDM
279
278
Если бы ассемблер произвел расширение для этого примера’’правильно”, то,
когда макроинструкция POP вызывалась бы, например, с аргументом 3 —
POP 3, — расширение было бы таким:
MOV (S3)+,R3
что, очевидно, является бессмыслицей и было бы помечено флагом ошибки.
Существуют два способа справиться с этой проблемой, рассмотрение одно-
го из которых мы отложим до следующего раздела. Простое средство ’’за-
добрить” ассемблер, чтобы он произвел желаемую подстановку, заключает-
ся в переопределении макроинструкции:
.MACRO SAVE А, В, С
MOV А, — (SP)
MOV B,-(SP)
MOV C,-(SP)
.ENDM
В этом случае вместо обращения к макроинструкции SAVE 1,2,4 мы вызы-
ваем ее с помощью SAVE Rl, R2, R4. При расширении макроинструкпии ге-
нерируется показанный выше код, и ассемблер выполняет необходимые под-
становки. На этот раз он находит вхождение терма А и заменяет его его зна-
чением, а именно R1. Аналогично, и с В и С он обходиться так, как мы того и
ожидаем (рис. 13.5.2).
Фактически целевое назначение этой макроинструкции намного шире то-
го, к чему мы первоначально стремились, поскольку она помещает в стек не
только содержимое указанных регистров, но может поместить в него что
угодно. Например, если мы хотим сохранить содержимое R1 и R5, а также со-
держимое ячейки памяти с символьным адресом ADDR, то можем вызвать
эту макроинструкцию так, как показано на рис. 13.53. Аналогично мы мо-
жем сохранить содержимое этих двух регистров, а также поместить в стек
адрес ADDR, а не содержимое этой ячейки (рис. 13.5.4).
Передача аргументов макроинструкции — это намного более мощная кон-
цепция, чем, вероятно, читателю представляется. В силу способа обработки
ассемблером этих аргументов в качестве аргумента может быть передано
почти что угодно. Давайте еще раз посмотрим на процесс расширения макро-
инструкции. Когда ассемблер встречает обращение к макроинструкции, он
находит ее имя в таблице макроопределений и просто заменяет вызов макро-
инструкции ее определением. Для этого ассемблер вставляет исходные стро-
ки — цепочки знаков в коде ASCII — составляющие определение. Затем он
ищет формальные аргументы и, когда находит их, заменяет фактическими
дубликатами, которые опять являются просто цепочками знаков в коде
ASCII.Наконец,ассемблер возвращается к началу макрорасширения и начина-
ет обработку этого кода для первого прохода. Чтобы дать какое-то представ-
ление о возможностях этой концепции, мы покажем следующую макроин-
струкцию, которой в качестве аргумента передается целая мнемоническая
инструкция ЭВМ PDP-11. Таким путем можно создать удобные макроин-
струкции для самых разнообразных целей (рис. 13.55). (Мы не заявляем
о каких-либо заслугах этой макроинструкции, а приводим ее лишь как ил-
люстративный пример.) Обратите внимание, что ассемблер правильно заме-
1 • LIST ME
2 (
3 .MACRO SAVE A*B»C (МАКРООПРЕДЕЛЕНИЕ
4 NOV A*-(SP) (ПОМЕСТИТЬ
5 NOV B*-(SP) : ТРИ OS’EKTA
6 NOV C.-(SP) ( В СТЕК
7 .ENON ;КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
8 (
9 000000 START: SAVE R1*R2*R4 ;ВЫЗВАТЬ NAKPOC
000000 010146 NOV Rb-(SP) (ПОМЕСТИТЬ
000002 010246 MOV R2.-(SP) 1 ТРИ OS’EKTA
000004 010446 NOV R4.-1SP) f В СТЕК
10 000006 j(ПРОДОЛЖЕНИЕ
( ПРОГРАММЫ)
Рис. 13.5.2
1 .LIST ME
2 •
3 .MACRO SAVE A*B*C ;МАКРООПРЕДЕЛЕНИЕ
4 MOV A.-(SP) ;ПОМЕСТИТЬ
5 MOV B>-<SP) 1 ТРИ OS’EKTA
6 MOV C.-(SP) ; В СТЕК
7 .ENDM (КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
8
9 000000 START: SAVE R1*ADDR*R5 ;ВЫЗВАТЬ МАКРОС
000000 010146 MOV Rl.-(SP) (ПОМЕСТИТЬ
00000? 016746 мои ADDR.-(SP) ; ТРИ OS’EKTA
001004
000006 010546 MOV R5»-(SP) ( В СТЕК
10 000010 ;(ПРОДОЛПЕНИЕ
; ПРОГРАММЫ)
33 00101? 000102 ADDR: .WORD 102
Рис. 13.5.3
1 .LIST ME
2 ?
3 .MACRO SAVE AiB»C МАКРООПРЕДЕЛЕНИЕ
4 MOV A.-(SP) (ПОМЕСТИТЬ
5 MOV B»-(SP) ( ТРИ OS’EKTA
6 NOV C.-(SP) 1 В СТЕК
7 .ENDM (КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
8 9 000000 START: SAVE R1.9ADDR.R5 (ВЫЗВАТЬ МАКРОС
000000 010146 MOV Rb-(SP) (ПОМЕСТИТЬ
000002 012746 MOV »ADDR>-(SP) ( ТРИ OS’EKTA
001012
000006 010546 MOV R5*-(SP) ! В СТЕК
10 000010 ((ПРОДОЛЖЕНИЕ
( ПРОГРАММЫ)
33 001012 000102 ADDR: /WORD 102
Рис. 13.5.4
1 .LIST ME
2 F з .MACRO CUSTOM REG*INS (МАКРООПРЕДЕЛЕНИЕ
4 CLR REG (ОЧИСТИТЬ РЕГИСТР
5 INS (("САМОДЕЛЬНАЯ" ИНСТРУКЦИЯ)
6 .ENDM
7 »
8 000000 START: CUSTOM R2.SEC (ВЫЗВАТЬ МАКРОС
000000 005002 CLR R2 (ОЧИСТИТЬ РЕГИСТР
00000? 000261 SEC (("САМОДЕЛЬНАЯ" ИНСТРУКЦИЯ)
9 000004 ...... I(ПРОДОЛЖЕНИЕ
( ПРОГРАММЫ)
Рис. 13.5.5
280
281
1 2 .LIST KE
А 3 4 5 6 7 8 000980 000000 000002 ♦ 000010 005002 016767 009000 000000 START: .MACRO CLR IMS .EN8M CUSTOM CLR MOV CUSTOM REG R2.MGV R2 REG,IMS 13, R4 ;МАКРООПРЕ«ЕЛЕНИЕ ЮЧИСТИТЬ РЕГИСТР I("САМОДЕЛЬНАЯ' ИНСТРУКЦИЯ) ; ВЫЗВАТЬ МАКРОС ДОЧИСТИТЬ РЕГИСТР 1 < "САМОДЕЛЬНАЯ" ИНСТРУКЦИЯ) ;(ПР0Д0Л1ЕНИЕ ; ПРОГРАММЫ)
Рис. 13.5.6
1 .LIST ME
3 4 5 6 7 MACRO CLR IMS .ENDM CUSTOM REG REG,INS ! МАКРООПРЕДЕЛЕНИЕ ;ОЧИСТИТЬ РЕГИСТР ><"САМОДЕЛЬНАЯ" ИНСТРУКЦИЯ)
8 000000 000090 000002 9 000006 005002 012704 000003 START: CUSTOM CLR MOV R2»(M0V R2 «, R4 «,R4> ;ВЫЗВАТЬ МАКРОС ;ОЧИСТИТЬ РЕГИСТР ;<"САМОДЕЛЬНАЯ" ИНСТРУКЦИЯ) ;(ПР0Д0Л1ЕНИЕ ; ПРОГРАММЫ)
Рис. 13.5.7
нил формальный аргумент INS на SEC и верно сгенерировал машинный код
для инструкции ”установить-бит-переноса”.
Рассмотрим другой программный сегмент, в котором вызывается та же
самая макроинструкция CUSTOM, но на этот раз на месте формального ар-
гумента INS передается аргумент MOV #3,R4 (рис. 13.5.6). Как видим, де-
ла здесь идут скверно. Проблема возникает из-за того, что ассемблер интер-
претирует MOV #3,R4 как три фактических аргумента, а именно MOV, #3
и R4. Он заменяет REG цепочкой R2, INS — цепочкой MOV, а #3 и R4 оказы-
ваются ненужными аргументами (напоминаем, что пробелы между MOV и
#3 рассматриваются ассемблером как разрешенные разделители в списке ар-
гументов) . Очевидно, нам необходимы какие-то средства, чтобы можно бы-
ло сообщить ассемблеру, что он должен рассматривать MOV #3,R4 как
один фактический аргумент, который должен подставляться вместо фор-
мального аргумента INS. Это мы делаем, заключая строчку в угловые скоб-
ки (<,>), как показано на рис. 135.7.
В заключение этого раздела мы последний раз посмотрим на процесс, с
помощью которого ассемблер заменяет формальные аргументы фактически-
ми. Вспомним определение нашей в конце концов успешной макроинструк-
ции SAVE:
.MACRO SAVE А, В, С
MOV А, — (SP) ; PUT THREE
MOVB,-(SP) ; THINGS
MOV C, - (SP) ; ON THE STACK
.ENMD
Предположим, что мы изменили в этих строках комментарии так, что они те-
перь читаются следующим образом:
1 .LIST ME
2 3 4 5 6 7 8 9 000000 START: I .MACRO NOV MOV MOV .ENDM J SAVE SAVE A,B,C A,-<SP) B,-(SP) C,-(SP) R1,R2*R4 ;МАКРООПРЕДЕЛЕНИЕ fPUT A S FEW THINGS ; ON THE STACK ;КОНЕЦ МАКРООПРЕДЕЛЕНИЯ ;ВЫЗВАТЬ МАКРОС
000000 010146 000002 010246 000004 010446 10 000006 Рис. 13.5.8 NOV MOV MOV R1,-(SP) R2,-(SP> R4,-CSP) fPUT Rl J FEW THINGS ; ON THE STACK >(ПРОДОЛЖЕНИЕ » ПРОГРАММЫ)
; PUT А
; FEW THINGS
; ON THE STACK
А посмотрите, что происходит, когда вызывается эта макроинструкция (рис.
13.5.8). В первой строке макрорасширения ассемблер правильно заменил
терм А цепочкой R1, что дает
MOV Rl,—(SP)
Однако на этом он не остановился; он заменил А цепочкой R1 также и в
комментариях, что привело к такому бессмысленному комментарию:
; PUT Rl
Хотя это может показаться лишь забавным и никакого вреда в данном слу-
чае, конечно, не приносит, все же отсюда видно, как буквально ассемблер ре-
шает эту задачу. Чтобы избежать непредвиденных проблем, мы должны учи-
тывать этот ’’рабский” подход ассемблера к выполнению данной работы.
13.6. ПЕРЕДАЧА АРГУМЕНТОВ МАКРОИНСТРУКЦИИ С ПОМОЩЬЮ .
КОНКАТЕНАЦИИ
Вспомним одну из наших неудач из предыдущего раздела. Мы включили
в макроопределение ’’инструкцию”
MOV RA, — (SP)
а при вызове макроинструкции передали фактический аргумент 1 на месте
формального аргумента А. Конечно, А заменено на 1 не было, и теперь мы,
вероятно, считаем эти наши прежние попытки слишком упрощенными. Тем
не менее кое-что здесь можно спасти.
Просматривая, что же происходит неверно, мы решили, что ассемблер не
рассматривает А в RA как распознаваемое вхождение знака А; вместо этого
он считает RA единым неделимым термом. Нам нужна какая-то схема, чтобы
отделить А от R. Мы могли бы попытаться записать так:
R,A
или так:
RA
но ассемблер сгенерировал бы просто
R, 1
282
283
соответственно, но ни то, ни другое не является тем, что нам нужно. Необхо-
дима какая-то пунктуация, которая уведомит ассемблер, что терм, к которо-
му она относится (в нашем случае А), является отдельным элементом, и
предпишет ассемблеру удалить эту пунктуацию при выполнении подстанов-
ки. Ассемблер ЭВМ PDP-11 для этой цели использует апостроф, а процесс
подстановки в этом случае называется конкатенацией, поскольку происхо-
дит сцепление строчки фактического аргумента с тем, что имеется в исход-
ном коде. Пример прояснит эту идею.
Рассмотрим макроинструкцию, которая увеличивает старший байт задан-
ного регистра (напоминаем, что к старшему байту регистра непосредственно
обращаться невозможно):
:MACRO INCHIGHX
SWAB R’X
INCB R’X
SWAB R’X
.ENDM
Когда эта макроинструкция вызывается, например, как INCHIGH 3, каждое
вхождение терма X заменяется фактическим аргументом 3 и выполняется
конкатенация 3 с предшествующим термом R, что дает следующее расшире-
ние макроинструкции:
SWAB R3
INCB R3
SWAB R3
Это именно то действие, которое нас интересует. Теперь мы можем успешно
записать основной пример предыдущего раздела так, как показано в листин-
ге на рис. 13.6.1.
В качестве другого примера рассмотрим программу, в которой мы хотим,
чтобы в регистр R0 помещался код ASCII какого-то конкретного знака
(опять требуемая здесь инструкция, без сомнения, может быть закодирована
непосредственно в программе без использования макроинструкции, но этот
простой пример иллюстрирует одну из особенностей конкатенации). Для вы-
полнения этой работы мы пишем макроинструкцию, содержащую лишь одну
строку инструкции:
.MACRO SETCODE CHAR
MOVB #’CHAR,R0
.ENDM
В строке, где код ASCII знака пересылается в R0, мы использовали преиму-
щество конструкции с апострофом ассемблера, в ответ на которую ассемб-
лер генерирует код ASCII заданного знака. Но, как видно из рис. 13.6.2, ко-
гда мы вызываем эту макроинструкцию, чтобы поместить код знака Н в R0,
то сталкиваемся с некоторыми трудностями. Проблема очевидна. Вместо то-
го, чтобы просто подставить Н взамен формальной переменной CHAR, ассемб-
лер выполняет конкатенацию Н, в процессе которой удаляет апостроф. Чтобы
разрешить эту проблему, необходимо более внимательно посмотреть на то,
как ассемблер обходится с конструкцией с апострофом.
Когда в процессе расширения макроопределения ассемблер встречает апо-
строф, непосредственно предшествующий формальному аргументу, он удаля-
284
1 • LIST ME
2
3 .MACRO SAVE A»B»C (НАКРООПРЕДЕЛЕНИЕ
4 MOV R’At-(SP) (СОХРАНИТЬ
5 MOV R’B»-(SP) » ТРИ
6 MOV R’C»-(SP> ( РЕГИСТРА
7 .ENDM (КОНЕЦ МАКРООПРЕДЕЛЕНИЯ
8 (
9 000000 START: SAVE 1,3:5 (ВЫЗВАТЬ МАКРОС
000000 010146 MOV Rlr-(SP) (СОХРАНИТЬ
000002 010346 MOV R3»-(SP) ( ТРИ
000004 010546 MOV R5»-(SP) ( РЕГИСТРА
10 000006 ((ПРОДОЛЖЕНИЕ
; ПРОГРАММЫ»
Рис. 13.6.1
1 2 3 4 5 6 7 8 000000 START: .LIST .MACRO MOVB .ENDM SETCODE ME
SETCODE CHAR »»CHAR»RO H (МАКРООПРЕДЕЛЕНИЕ (ЗАНЕСТИ КОД ASCII ДЛЯ ( ЗНАКА В R0 (УСТАНОВИТЬ С (RO) = ASCII ’Н
и 9 000000 000004 112700 000000 MOVB €H»RO (ЗАНЕСТИ КОД ASCII ДЛЯ ( ЗНАКА В R0 ((ПРОДОЛЖЕНИЕ ; ПРОГРАММЫ)
Рис. 13.6.2
1 .LIST ME
2 !
3 .MACRO SETCODE CHAR (МАКРООПРЕДЕЛЕНИЕ
4 MOVB t” CHAR-RO (ЗАНЕСТИ КОД ASCII ДЛЯ
5 ( ЗНАКА В RO
6 .ENDM
7 !
8 000000 START: SETCODE Н (УСТАНОВИТЬ С(RO) = ASCII ’Н’
000000 112700 MOVB t’H-RO (ЗАНЕСТИ КОД ASCII ДЛЯ
000110
( ЗНАКА В R0
9 000004 ((ПРОДОЛЖЕНИЕ
; ПРОГРАММЫ)
Рис. 13.6.3
ет этот апостроф и заменяет формальный аргумент соответствующим ему
фактическим аргументом. Если, кроме того, за формальным аргументом
следует апостроф, то ассемблер удаляет также и этот второй апостроф. Та-
ким образом, выполняя подстановки, ассемблер удаляет апостроф (или апо-
строфы) , непосредственно предшествующий или непосредственно следую-
щий за формальной переменной, которая должна быть заменена, но только
эти апострофы. Решение проблемы теперь ясно. Вместо того, чтобы писать
MOVB #’CHAR,R0, мы используем двойной апостроф: MOVB #”CHAR,R0.
При расширении макроинструкпии апостроф, непосредственно предшеству-
ющий формальной переменной CHAR, вызовет конкатенацию, но будет так-
же и удален; при этом останётся один апостроф, показывающий на необходи-
мость генерации кода ASCII заданного знака. Листинг на рис. 13.6.3, пред-
ставляющий исправленную версию программного сегмента, показывает, что
такая модификация решает проблему.
285
13.7. ЛОКАЛЬНЫЕ СИМВОЛЫ
В ходе ассемблирования программы пользователя ассемблер делит исход-
ный код на блоки, определяемые следующим образом. Первый блок состоит
из строк исходного кода от начала программы до первой строки, содержа-
щей нормально сформатированное определение символа (метку), т. е. сим-
вол, за которым следует двоеточие в противоположность прямому присва-
иванию значения символу. Следующий блок простирается до следующего
нормально сформатированного определения символа и т. д. Последний блок
начинается от последнего определения символа и заканчивается оператором
END. Существует также несколько других способов начинать и заканчивать
эти блоки, но здесь они не приводили бы к каким-либо заметным послед-
ствиям. Заинтересованных читателей отсылаем к соответствующему руко-
водству по ассемблеру1.
Внутри каждого из этих блоков программист может определять локаль-
ные символы, которые ведут себя подобно любым другим символам (START,
ADDR и т. п.) во всех отношениях за двумя исключениями. Во-первых, име-
нем локального символа всегда является целое число п в диапазоне от 1 до
65535, за которым следует знак доллара.Таким образом, 1$,57$ и 32768 $ —
это имена локальных символов. Во-вторых, локальный символ имеет значе-
ние только внутри того блока, в котором он определяется. Из этой особен-
ности вытекают два следствия. Одно состоит в том, что ссылка в одном бло-
ке на локальный символ из другого блока будет помечена флагом ошибки,
поскольку такая ссылка будет рассматриваться как неопределенная. Второе
следствие заключается в том, что один и тот же локальный символ может
использоваться в одной и той же программе неоднократно, не приводя к
ошибкам многократного определения символов при условии, что определе-
ния двух таких локальных символов никогда не появляются в одном и том
же блоке.
Поскольку блоки, на которые ассемблер делит исходный код, определя-
ются специально с целью обеспечения возможности использования этих ло-
кальных символов, они называются блоками локальных символов. Следую-
щий пример пояснит эти идеи. Нас не занимает функция программы, пока-
занной на рис. 13.7.1; нас больше интересуют блоки локальных символов и
то, как трактуются локальные символы. Заинтересованный читатель может
проследить логику программы, чтобы посмотреть, что она делает. Обратите
внимание, что мы не включили комментариев для облегчения этой задачи, но
строка текста ASCII в коде внизу показывает, что программа размещает чис-
ла в некотором порядке. Фактически это решение упражнения 7.7.7.
Мы видим, что здесь есть два блока локальных символов, состоящих из
строк от 2-й до 15-й и строк от 1741 до 21-й. В первом из этих блоков опреде-
ляются символы 29$, 12$ и 62$ и к ним производится обращение. Во вто-
ром блоке определяется только символ 12$. Обратите внимание, что, хотя
символ 12$ определяется дважды, ассемблер не сообщает о конфликте, ко-
торый бы возник в результате многократного определения символа, посколь-
1 Полезно будет также обратиться к книге Вигдорчика Г. В., Воробьева А. Ю., Пра-
ченко В. Д. Основы программирования на ассемблере для СМ ЭВМ. - М.: Финансы и ста-
тистика, 1983. - 256 с. - Прим, перев.
286
1 000000 START: «IN.DEC «BLOCK»B1
2 000034 016746 MOV BLOCK»-CSF)
000200
3 000040 *IN.DEC «BLOCK»ISP)
4 000072 011600 MOV (SPJ.RO
5 000074 005300 ВЕС RO
6 000076 010001 29* s MOV RQrRl
7 000100 012702 MOV «BLOCK.R2
000240’
8 000104 022212 12*: CMP (R2)+.(R2)
9 000106 003405 BLE 62*
10 000110 011246 MOV <R2)»-(SP>
11 000112 016212 MOV -2<R2)»CR2)
177776
12 000110 012662 MOV CSP)*»-2(R2)
177776
13 000122 077110 62*: SOB Rlt12*
14 000124 077014 SOB RO.29*
15
16 000126 PRINT: «OUT.ASC 012*
17 000156 MOOT.DEC «BLOCK.<SP)+
18 000210 «EXIT
19
20 000212 124 12*: .ASCII /THE NUMBERS IN ORDER:/
000213 110
000214 105
0бО215 040
000216 116
000217 125
000220 115
000221 102
000222 105
000223 122
000224 123
000225 040
000226 111
000227 116
000230 040
000231 117
000232 122
000233 104
000234 105
000235 122
000236 072
000237 ООО
21 .EVEN
22
23 000240 BLOCK: .BLKW 20
24
25 000000» .END START
ТАБЛИЦА СИМВОЛОВ
BLOCK 000240R START OOOOOOR к.... ' ««»««« G
PRINT 000126R
РИС. 13.7.1
ку определения символа 12$ оказываются в разных блоках локальных сим-
волов. Случайно к символу PRINT в программе нигде обращения нет - его
единственным назначением было закончить первый блок локальных симво-
лов и начать второй блок. Наконец, заметьте, что символы 29$, 12$ и 62$
не появляются в таблице символов, хотя они являются символами, опреде-
ленными пользователем.
Теперь, когда мы видим, что можем использовать локальные символы,
возникает вопрос, а будем ли мы это делать? То есть станем ли мы обычно в
повседневном программировании пользоваться этими локальными символа-
ми вместо нормально сформатированных? Ответ — вероятно, нет. Поскольку
локальные символы не делают ничего нового по сравнению с нормально
287
сформатированными, то мало причин их применять. Есть случаи, в которых
мы можем их использовать, но они редки. Реальную полезность локальных
символов мы увидим в следующем разделе, где покажем, что при некоторых
обстоятельствах мы можем предписывать ассемблеру, чтобы он генерировал
эти локальные символы автоматически.
13.8. АВТОМАТИЧЕСКИ ГЕНЕРИРУЕМЫЕ ЛОКАЛЬНЫЕ СИМВОЛЫ
Рассмотрим программу, в которой нам часто приходится определять, ка-
кой из двух регистров содержит большее число. Более точно, мы стремимся
узнать, какое число больше. Такая ситуация обычно располагает к написа-
нию макроинструкции:
.MACRO BIGGER А, В, С ; МАКРООПРЕДЕЛЕНИЕ
MOV R’A, R’C ; ДОПУСТИТЬ, ЧТО ПЕРВОЕ БОЛЬШЕ
CMP R’A, R’B ; СРАВНИТЬ
BGE DONE ; ПРЕДПОЛОЖЕНИЕ ВЕРНО,
MOV R’B, R’C ; ИНАЧЕ - ДРУГОЕ БОЛЬШЕ
DONE: ; (.ENDM НЕ МОЖЕТ БЫТЬ
; ПОМЕЧЕНО)
.ENDM
Эта макроинструкция работает так. Ей передаются номера регистров А,В и С.
Первые два регистра подлежат проверке, а в третий будет помещаться наи-
большее из двух чисел. Логика весьма проста. Содержимое первого регистра
полагается большим и пересылается в третий регистр. Затем сравнивается со-
держимое первого и второго регистров. Если содержимое первого регистра
больше, чем второго, то предположение верно, и делать больше нечего. Если со-
держимое второго больше, чем первого, то оно пересылается в третий регистр.
Читатель, возможно, несколько озадачен меткой DONE на строке без ка-
кого-либо исходного кода. DONE — это метка для следующей строки про-
граммы, но где она должна ставиться? Если бы она использовалась как мет-
ка для оператора .ENDM (DONE: .ENDM), то создались бы трудности, по-
скольку при расширении макроинструкции ассемблер не расширяет опера-
тор .ENDM, так как .fiNDM есть просто директива, сигнализирующая о кон-
це макроопределения. Из программы на рис. 13.8.1 видно, что DONE имеет
правильное значение, т. е. значение адреса следующей инструкции программы.
По аналогичным причинам не следует ставить метки на оператор .MACRO.
Макроинструкция BIGGER в этом программном сегменте вызывается
дважды и в каждом случае, как мы видим, расширяется правильно. Однако
символ DONE оказывается определенным многократно. Когда макроин-
струкция расширяется в первый раз, символ DONE получает значение 002112.
При втором расширении символу DONE присваивается значение 003254. По-
скольку ассемблер считает такую ситуацию неприемлемой, он помечает эти
строки флагом ошибки М (многократно определенный символ). В результа-
те многократного определения метки DONE ассемблер помечает флагами
также некоторые другие инструкции.
Если бы мы подумали об этом заранее, то могли бы предвидеть проблему.
Теперь, когда нам известно о ее существовании, как мы можем с ней спра-
виться? Один способ состоит в передаче макроинструкции четвертого аргу-
мента, который должен использоваться в качестве метки на месте DONE
(рис. 13.8.2). Это довольно хорошее решение проблемы. Когда макроин-
1 .LIST ME
2 3 4 5 4 7 8 9 10 11 12 001000 DONE: START: t .MACRO MOV CMP DSE MOV .ENDM BIG6ER R’ArR’C R’A,R’B DONE R’B.R’C A.B.C (МАКРООПРЕДЕЛЕНИЕ ;ДОПУСТИТЬ, ЧТО ПЕРВОЕ ВОЛЬВЕ (СРАВНИТЬ (ПРЕДПОЛОЖЕНИЕ ВЕРНО, ( ИНАЧЕ — ДРУГОЕ ВОЛЬВЕ ((.ENDM НЕ МОЖЕТ БИТЬ ; ПОМЕЧЕНО) ((НАЧАЛО ПРОГРАММЫ)
и 44 002107 002102 002104 002106 002110 002112 010105 020102 002001 010705 DONE: BIGGER MOV CMP B6E MOV 1.2.5 R1.R5 R1.R2 DONE R2.R5 (ВЫЗВАТЬ МАКРОС (ДОПУСТИТЬ, ЧТО ПЕРВОЕ ВОЛЬДЕ (СРАВНИТЬ (ПРЕДПОЛОЖЕНИЕ ВЕРНО, ; ИНАЧЕ — ДРУГОЕ БОЛЬВЕ ((.ENDM НЕ МОЖЕТ БЫТЬ ( ПОМЕЧЕНО)
•
АО М 73 003244 003244 003246 003250 003252 003254 010201 020204 002377 010401 DONE: BIGGER MOV CMP sue MOV • 2,4.1 R2.R1 R2.R4 DONE R4.R1 (ВЫЗВАТЬ МАКРОС СНОВА (ДОПУСТИТЬ, ЧТО ПЕРВОЕ ВОЛЬДЕ (СРАВНИТЬ (ПРЕДПОЛОЖЕНИЕ ВЕРНО, ( ИНАЧЕ -- ДРУГОЕ БОЛЬВЕ ( С. ENDM НЕ МОЖЕТ БЫТЬ ( ПОМЕЧЕНО)
Рис. . 13.8.1 •
1 2 3 4 5 6 7 8 9 10 11 12 001000 D: START: LIST .MACRO MOV CMP BGE MOV .ENDM • ME BIGGER R’A,R’C R’A,R’B D R’B,R’C A.B.C.D (МАКРООПРЕДЕЛЕНИЕ (ДОПУСТИТЬ, ЧТО ПЕРВОЕ БОЛЬВЕ (СРАВНИТЬ (ПРЕДПОЛОЖЕНИЕ ВЕРНО, ! ИНАЧЕ — ДРУГОЕ БОЛЬВЕ ((.ENDM НЕ МОЖЕТ БЫТЬ ( ПОМЕЧЕНО) ((НАЧАЛО ПРОГРАММЫ)
44 002102 002102 002104 002106 002110 002112 010105 020102 002001 010205 DONE: • BIGGER MOV CM® BGE NOV 1,2,5,DONE R1,R5 R1.R2 DONE R2.R5 (ВЫЗВАТЬ МАКРОС (ДОПУСТИТЬ, ЧТО ПЕРВОЕ БОЛЬВЕ (СРАВНИТЬ (ПРЕДПОЛОЖЕНИЕ ВЕРНО, ( ИНАЧЕ -- ДРУГОЕ БОЛЬВЕ ((.ENDM НЕ МОЖЕТ БЫТЬ ( ПОМЕЧЕНО)
73 003244 003244 003246 003250 003252 003254 010201 020204 002001 010401 D0NE1: BIGGER MOV CMP BGE MOV 2,4,1,DONE1 R2.R1 R2.R4 DONE! R4.R1 (ВЫЗВАТЬ МАКРОС СНОВА (ДОПУСТИТЬ, ЧТО ПЕРВОЕ БОЛЬВЕ (СРАВНИТЬ (ПРЕДПОЛОЖЕНИЕ ВЕРНО, ( ИНАЧЕ — ДРУГОЕ БОЛЬВЕ ((.ENDM НЕ МОЖЕТ БЫТЬ ( ПОМЕЧЕНО)
Рис. 13.8.2
288
10 Зак. 2212
289
струкция вызывается первый раз, мы передаем значение DONE, которое
должно использоваться на месте метки D. Во второй раз мы используем для
этой метки значение DONE1 и, как видно из листинга, никакого конфликта
с именами символов теперь нет. Обратите внимание, что при условии доста-
точного внимания с нашей стороны мы могли бы передать макроинструкции
на месте DONE и DONE1 локальные символы, такие как 1$ или 54$.
Хотя мы отыскали средства обойти проблему многократно определенного
символа, полностью трудности нам преодолеть еще не удалось. Все макроин-
струкции, которые мы определили в этой главе, очень короткие и простые,
поскольку, в конце концов, они являются иллюстративными примерами и не
обязательно должны использоваться в реальном программировании. Пред-
положим, однако, что по логике некоторой макроинструкции ей требуется
шесть уникальных меток и она вызывается 30 раз в одной программе. Обра-
щение к этой макро инструкции потребует, чтобы программист построил 180
различных имен символов, которые, конечно, должны отличаться от других
имен символов, используемых в программе. Хотя обрисованная ситуация мо-
жет быть экстремальной, все же ясно, что поименование символов, передава-
емых макроинструкции, может стать для программиста тяжелой обузой.
К счастью, ассемблер макроинструкций ЭВМ PDP-11 еще раз приходит нам
на помощь.
Если в операторе прототипа макроинструкции одному или нескольким
формальным аргументам предшествует вопросительный знак, то ассемблер
при расширении макроинструкции действует следующим образом. Если для
одного из формальных аргументов с вопросительным знаком в его начале
поставляется фактический аргумент при вызове макроинструкции, то ас-
семблер обходится с ним обычным способом, т. е. выполняет подстановку
фактического аргумента вместо формального. Однако, если для одного из
этих специальных формальных аргументов фактический аргумент не постав-
ляется, то ассемблер для такого формального аргумента обеспечит следую-
щий доступный локальный символ.
Рассмотрим оператор прототипа макроса .MACRO TEST А,?В,С. Если
макроинструкция вызывается, например, как TEST 1, 2, 3, то для каждого
формального аргумента поставляется соответствующий фактический аргу-
мент. С другой стороны, если макроинструкция вызывается как TEST 1,, 3,
то А получит значение 1,С получит значение 3,но В не получит никакого зна-
чения. В качестве другого примера рассмотрим .MACRO DUMMY X,?Y. Вы-
зов DUMMY 5, ADDR поставляет значения как обычно: X = 5, Y = ADDR. Но
DUMMY 5 для X поставляет значение 5, а для Y не дает никакого значения.
Именно это мы имеем в виду, когда говорим, что для формального аргумен-
та поставляется или не поставляется значение.
Если для формального аргумента значение в вышеописанном смысле не
поставляется, то способ обработки его ассемблером зависит от типа этого
формального аргумента. Если это ’’нормальный” формальный аргумент, т. е.
ему не предшествует вопросительный знак, то ассемблер присвоит ему зна-
чение пустой строки знаков. Таким образом, например, если макроинструк-
ция TEST определяется как
.MACRO TEST А, В, С
CLR R’A
CLR R’B
CLR R’C
.ENDM
и вызывается как TEST 1,,4, то будет расширена следующим образом:
CLR R1
CLR R
CLR R4
Если формальному аргументу, значение для которого не поставляется, пред-
шествует вопросительный знак, то ассемблер присвоит ему в качестве значе-
ния следующий доступный локальный символ в этом блоке. Генерируя та-
кие локальные символы, ассемблер начинает с локального символа 64$. Ес-
ли ассемблер встречает следующий такой символ, то присваивает ему значе-
ние 65$ и т.п.Такая автоматическая генерация различных локальных симво-
волов продолжается до тех пор, пока не происходит одно из двух. Когда ас-
семблер вводит новый блок локальных символов, он начинает генерацию ло-
кальных символов снова с 64$, и поскольку это новый блок локальных сим-
волов, никаких конфликтов при определении символов не возникает. Когда
и если ассемблер сгенерирует локальные символы вплоть до 127$, он начнет
снова с 64$, а это уже подразумевает многократно определенные символы.
Таким образом, ассемблер будет автоматически генерировать локальные
символы внутри каждого блока локальных символов в диапазоне от 64$ до
127$; обычно этого диапазона нам хватает с избытком. Если же проблемы
возникают, то с ними можно справиться, просто вставив нормально сформа-
тированную метку где-то в программе, чтобы начать новый блок локальных
символов, причем не имеет значения, есть или нет в программе ссылки на
этот символьный адрес.
Теперь мы можем модифицировать нашу макроинструкцию BIGGER, ис-
пользуя возможность ассемблера генерировать локальные символы. Обрати-
те внимание, что каждый раз, когда макроинструкция вызывается, значение
для формальной переменной D не поставляется (рис. 13.8.3).
При определении макро инструкций, использующих автоматическую гене-
рацию локальных символов, предлагается такие формальные аргументы раз-
мещать в конце списка аргументов оператора прототипа макро инструкции.
Например, если трем аргументам X, Y и Z должны присваиваться значения
автоматически генерируемых локальных символов и если макро"йнструкция
определяется как
.MACRO TEST А, ?Х, ?Y, В, ?Z, С
то, чтобы придать А значение 1, В значение R3 и С значение MOV пришлось бы
вызывать макроинструкцию следующим образом:
TEST 1,,,R3, ,MOV
В то же время, если бы оператор прототипа макроинструкции был
.MACRO TEST А, В, С, ?Х, ?Y, ?Z
290
10*
291
1 .LIST №
2 {
3 .MACRO BIGGER A,B,C,?D {МАКРООПРЕДЕЛЕНИЕ
4 MOV R’A.R’C >ДОПУСТИТЬ. ЧТО ПЕРВОЕ БОЛЬШЕ
5 CMP R’A.R’B ;СРАВНИТЬ
6 BGE D 1 ПРЕДПОЛОЖЕНИЕ ВЕРНО.
7 MOV R’B.R’C ; ИНАЧЕ — ДРУГОЕ БОЛЬШЕ
8 D: .(.ENDM НЕ МОНЕТ БЫТЬ
? ; ПОМЕЧЕНО)
10 .ENDM
11 t
12 001000 STARTs {(НАЧАЛО ПРОГРАММЫ)
44 002102 BIGGER 1,2,5 {ВЫЗВАТЬ МАКРОС
002102 010105 MOV R1,R5 ;ДОПУСТИТЬ, ЧТО ПЕРВОЕ БОЛЬ«Е
002104 020102 СИР R1,R2 ; СРАВНИТЬ
002106 002001 BGE 64» {ПРЕДПОЛОЖЕНИЕ ВЕРНО,
002110 010205 002112 64»: MOV R2,R5 { ИНАЧЕ — ДРУГОЕ БОЛЬШЕ !(.ENDM НЕ МОЖЕТ БЫТЬ { ПОМЕЧЕНО)
73 003244 BIGGER 2,4,1 {ВЫЗВАТЬ МАКРОС СНОВА
003244 010201 MOV R2,R1 {ДОПУСТИТЬ, ЧТО ПЕРВОЕ БОЛЬШЕ
003246 020204 СМР R2,RZ {СРАВНИТЬ
003250 002001 BGE 65» {ПРЕДПОЛОЖЕНИЕ ВЕРНО,
003252 010401 MOV R4.R1 { ИНАЧЕ — ДРУГОЕ БОЛЬШЕ
003254 65»: {(.ENDM НЕ МОЖЕТ БЫТЬ
{ ПОМЕЧЕНО)
Рис. 13.8.3
то было бы достаточно вызывать ее так:
TEST l,R3,M0V
13.9. БЛОКИ ПОВТОРЕНИЯ
Блок повторения — это некоторый тип макро инструкции, значительно от-
личающийся рядом особенностей от тех, которые мы изучали до сих пор. Во-
первых, блоку повторения не присваивается имени, как обычной макроин-
струкции. Следовательно, его нельзя вызывать по имени, а его определение
не помещается в таблицу макроопределений. Вместо этого, всякий раз, ко-
гда в программе требуется блок повторения, он определяется в том месте,
где в нем возникает нужда, и ассемблер расширяет его тогда и там, когда
и где он встретит его определение. Во-вторых, блок повторения всегда со-
держит один и только один формальный аргумент, а подстановки для факти-
ческого аргумента перечисляются в определении блока повторения. Наконец,
ассемблер расширяет код, представляющий определение блока повторения
не один раз, а по одному разу для каждого фактического аргумента. Эти
идеи станут более понятными после ознакомления со спецификой формиро-
вания блока повторения.
Начало блока с неопределенным числом повторений обозначает не .М ACRO,
а директива ассемблера .IRP (Indefinite Repeat block) . На одной строке с ди-
рективой .IRP помещается имя единственного формального аргумента (X,
ARG или что-то в этом роде), за которым располагается запятая, за которой
следует список фактических аргументов, заключенный в угловые скобки,
292
где аргументы отделяются друг от друга запятыми или пробелами. Поэтому
типичная директива JRP выглядит,например, так:
.IRP Q,< 1,2, MOV Д3>
Затем следует строка или строки исходного кода инструкций, составляющих
определение блока повторения. Для обозначения конца макроопределения,
как и прежде, ставится директива .ENDM.
Когда ассемблер встречает директиву .IRP, он вставляет в этом месте про*
граммы исходный код, составляющий определение блока повторения и затем
заменяет формальный аргумент в этом исходном коде первым фактическим
аргументом из списка, соблюдая все правила подстановки аргументов — кон-
катенацию, прямую подстановку и т. п. Расширив блок повторения, ассемб-
лер затем расширяет его снова и заменяет формальный аргумент вторым
фактическим аргументом из списка. Таким образом, теперь имеются две ко-
пии определения, расположенные одна за другой. В первой копии формаль-
ный аргумент заменен первым фактическим аргументом, тогда как во вто-
рой копии его заменяет второй аргумент из списка. Этот процесс расширения
определения и замены формального аргумента продолжается до тех пор, по-
ка не исчерпается список фактических аргументов. Теперь блок повторения
расширен полностью.
Очевидно, что название "блок с неопределенным числом повторений" яв-
ляется подходящим. Определение (т. е. блок инструкций, составляющих
определение) повторяется неопределенное число раз-, число повторений зави-
сит от числа фактических аргументов в списке аргументов. В качестве про-
стого, но очень полезного примера рассмотрим однострочный блок с неопре-
деленным числом повторений, который сохраняет в стеке текущие значения
регистров 0, 2,3 и 4:
.IRP Х,<0,2,3,4>
MOV R’X, - (SP)
.ENDM
Исходный код, составляющий определение блока повторения, а именно,
MOV R’X, — (SP), будет вставлен в программу четыре раза, по одному для
каждого из аргументов 0, 2, 3 и 4. На рис. 13.9.1 показано, как ассемблер
ЭВМ PDP-11 справляется с этим блоком повторения.
Поскольку блок с неопределенным числом повторений не имеет имени,
его определение должно вставляться в программу всякий раз, когда оно не-
обходимо. Если это случается часто и становится поэтому надоедливым, то
проблему можно устранить, построив нормально сформатированную макро-
инструкцию (конечно, с именем) , определением которой является желаемый
1 .LIST ME
2
3 000000 STARTs .IRP X><0'2'3>4) ;ВЫЗВАТЬ БЛОК ПОВТОРЕНИЯ
4 MOV R»X»-(SP) {ПРОТОЛКНУТЬ РЕГИСТР
5 .ENDM »КОНЕЦ ОПРЕДЕЛЕНИЯ БЛОКА ПОВТОРЕНИЯ
000000 010046 MOV RO.-(SP) {ПРОТОЛКНУТЬ РЕГИСТР
000002 010246 MOV R2»-<SP) {ПРОТОЛКНУТЬ РЕГИСТР
000004 010346 MOV R3»-(SP) {ПРОТОЛКНУТЬ РЕГИСТР
000006 010446 MOV R4i-<SP) {ПРОТОЛКНУТЬ РЕГИСТР
6 {
7 000010 •(ПРОДОЛЖЕНИЕ
{ ПРОГРАММЫ)
Рис. 13.9.1
293
1 2 .LIST ME
3 MACRO SAVE A'B>CfD ;МАКРООПРЕДЕЛЕНИЕ
4 .IRP X»<A.B»C,D> ;ВЫЗВАТЬ БЛОК ПОВТОРЕНИЯ
5 MOV R’X.-(SP)
6 .ENDM ;КОНЕЦ ОПРЕДЕЛЕНИЯ БЛОКА ПОВТОРЕНИЯ
7 ENDM ;КОНЕЦ МАКРООПРЕДЕЛЕНИЯ ('SAVE’)
8
9 000000 STARTs SAVE 0»2»3»4 :ВЫЗВАТЬ МАКРОС
IRP Х.<0»2»3»4) ;ВЫЗВАТЬ БЛОК ПОВТОРЕНИЯ
MOV R’X»-<SP)
.ENDM ;КОНЕЦ ОПРЕДЕЛЕНИЯ БЛОКА ПОВТОРЕНИЯ
000000 010046 NOV RO»-(SP)
000002 010246 MOV R2»-(SP)
000004 010346 MOV R3»-(SP)
000006 010446 MOV R4»-(SP)
10 000010 ;(ПРОДОЛЖЕНИЕ
; ПРОГРАММЫ)
Рис. 13.9.2
блок повторения. Следовательно, предыдущий пример можно переписать за-
ново (рис. 13.9.2) и затем, когда необходимо, вызывать по имени. Как мно-
го исходного кода можно включать в определение блока повторений? В этом
отношении никаких ограничений нет, поэтому программист может поместить
в блок столько кода, сколько захочет, и этот код будет расширяться по од-
ному разу для каждого фактического аргумента. В действительности, как по-
казывает следующий пример, код, составляющий определение блока повто-
рения, может даже содержать другой блок повторения. Однако здесь требу-
ется определенное внимание, поскольку'фактический аргумент, используе-
мый в настоящий момент для внешнего блока повторения, может воздей-
ствовать на код, генерируемый во внутреннем блоке.
Следующий пример не имеет ничего общего с реальной действительностью,
он лишь призван показать, как блоки повторения могут вкладываться один в
другой и что при этом происходит. Этот программный сегмент просто ге-
нерирует код ASCII пар знаков, когда первый знак берется из множества
{А, В, С}, а второй — из множества {Х, Y} . Читателю будет полезно детально
просмотреть листинг на рис. 13.93, уделив особое внимание тому, какие
значения будут иметь различные формальные переменные при каждом рас-
ширении блока.
Поскольку пробелы считаются допустимыми разделителями в списке ар-
гументов блока повторения, то написанию аргументов следует уделить долж-
ное внимание. Как предполагается, блок повторения на рис. 13.9.4 должен
генерировать инструкцию CLR R1, сопровождаемую инструкцией, указанной
в списке аргументов. Как мы видим, ассемблер, в конце концов, обнаружил
четыре аргумента в списке - INC, Rl, DEC и Rl. Как и прежде, эту проблему
можно разрешить, используя угловые скобки для группирования символов
(рис. 13.9.5).
Существует модификация блока с неопределенным числом повторений,
в которой списком аргументов является цепочка знаков (рис. 13.9.6). Фор-
мат директивы ассемблера для этого блока такой:
.IRPC ARG,цепочка
где опять ARG — это формальный аргумент. Блок повторяется по одному
разу для каждого знака в цепочке, т. е. при каждом повторении ARG заменя-
ется знаком из цепочки. Поэтому, когда ассемблер, например, встречает
.IRPC X,A4R
294
1 .LIST ME
2 s
3 .IRP P»<A»B»C>
4 . IRP Q,(X,Y>
5 .ASCII Z’P”Q/
6 .ENDM
7 .ENDM
.IRP Q»(X,Y)
.ASCII ZA’QZ
.ENDM
000000 101 .ASCII /АХ/
000001 130
000002 101 .ASCII ZAYZ
000003 131
.IRP Q»<X>Y>
ASCII ZB’OZ
.ENDM
000004 102 .ASCII ZBXZ
000005 130
000006 102 ASCII ZBYZ
000007 131
IRP Q»<X»Y>
ASCII ZC’GZ
ENDM
000010 103 ASCII ZCXZ
000011 130
000012 103 ASCII ZCYZ
000013 131
Рис. 13.9.3
1 LIST ME
2
3 IRP A.(INC Rl.DEC Rl)
4 CLR Rl
5 A
6 ENDM
- 000000 005001 CLR Rl
A 000002 005267 000000 INC
000006 005001 CLR Rl
000010 000001 Rl
000012 005001 CLR Rl
A 000014 005367 000000 DEC
000020 005001 CLR Rl
000022 000001 Rl
Рис. 13.9.4
1 .LIST ME
2 f
3 .IRP At<<INC RDHDEC Rl>>
4 CLR Rl
5 A
6 .ENDM
000000 005001 CLR Rl
000002 005201 INC Rl
000004 005001 CLR Rl
000006 005301 DEC Rl
Рис. 13.9.5
1 .LIST ME
2 >
3 • IRPC X.0234
4 MOV R’Xt-(SP)
5 .ENDM
000000 010046 MOV RO»-(SP)
000002 010246 MOV R2.-CSP)
000004 010346 NOV R3»-(SP>
000006 010446 MOV R4.-(SP)
Рис. 13.9.6
295
1 .LIST ME
2 р
3 .IRPC X,<0234)
4 MOV R’Xt-(SP)
5 .ENDM
000000 010046 MOV RO.-(SP)
000002 010246 MOV R2»-<SP)
000004 010346 MOV R3.-(SP)
000006 010446 MOV R4.-CSP)
Рис. 13.9.7
1 .LIST НЕ
2 ;
а 3 .IRPC L,OH» BOY!
4 .ASCII /L/
5 .ENBM
OOOOOO 117 .ASCII /0/
000001 110 .ASCII /Н/
Рис. 13.9.8
1 .LIST ME
2 ;
Q 3 .IRPC L.OH BOY!
4 .ASCII /L/
5 .ENDM
OOOOOO 117 .ASCII /0/
000001 110 .ASCII /Н/
Рис. 13.9.9
он расширяет макроопределение один раз, заменяя X знаком А, еще раз, за-
меняя знаком 4, и еще раз, заменяя знаком R. Обратите внимание, что угло-
вые скобки по краям списка аргументов здесь не требуются. Таким образом,
блок повторения, разработанный ранее в этом разделе для сохранения четы-
рех регистров в стеке, можно переписать так, как показано на рис. 13.9.6. Хо-
тя угловые скобки по краям списка аргументов не нужны, они могут исполь-
зоваться, и в этом случае воспринимаются как группирующие символы и не
интерпретируются как знаки из цепочки, что видно из программного сегмен-
та на рис. 13.9.7.
Существуют случаи, в которых эти угловые скобки все-таки требуются,
Рассмотрим задачу генерации кодов ASCII знаков в цепочке ОН, BOY! на
познаковой основе. Мы можем попытаться справиться с этой (очевидно, на-
думанной) проблемой с помощью конструкции
.IRPC Х,ОН, BOY!
.ASCII /X/
.ENDM
но, как видно из рис. 13.9&, ассемблер, в конце концов, воспринял запятую
в этой цепочке в качестве разделителя и предположил,что переданной строкой
было просто ОН. Если, признавая поражение, мы сотрем запятую из цепочки,
ни к чему хорошему это не приведет, поскольку теперь ассемблер будет счи-
тать разделителем пробел (рис. 13.9.9). Мы решим проблему, заключив це-
почку в угловые скобки, которые ассемблер будет интерпретировать как
группирующие символы, а не как часть самой цепочки (рис. 13.9.10).
Существует последняя форма блока повторения, которая используется не
так часто, но иногда оказывается кстати. Здесь директивой ассемблера явля-
296
1 .LIST ME
2 •
3 Л RPC Lt<OHt BOYD
4 .ASCII ZLZ
5 .ENDM
000000 117 .ASCII /0/
000001 110 .ASCII ZHZ
000002 054 .ASCII />/
000003 040 ASCII / /
000004 102 .ASCII ZBZ
000005 117 .ASCII /0/
000006 131 .ASCII ZYZ
000007 041 .ASCII Z!Z
Рис. 13.9.10
1 .LIST ME
2 s
3 000016 .REPT 14
4 .WORD 0
5 .ENDM
000000 000000 .WORD 0
000002 000000 .WORD 0
000004 000000 .WORD 0
000006 000000 .WORD 0
000010 000000 .WORD 0
000012 000000 .WORD 0
000014 000000 .WORD 0
000016 000000 .WORD 0
000020 000000 .WORD 0
000022 000000 .WORD 0
000024 000000 .WORD 0
000026 000000 .WORD 0
000030 OOOOOO .WORD 0
000032 000000 .WORD 0
Рис. 13.9.11
ется КЕРТ п, а код, составляющий определение блока повторения, просто
повторяется п раз. Обратите внимание, что формальный аргумент отсутству-
ет, и, следовательно, никакой подстановки аргументов не производится. При-
думать полезный пример нелегко, но рассмотрим проблему генерации блока
из 14ю последовательных слов оперативной памяти, каждое из которых со-
держит 0. Один способ справиться с этой работой состоит в использовании
директивы
.WORD 0, 0,0, 0,0, 0,0,0, 0, 0, 0, 0,0, 0
но легче использовать тип блока повторения, показанный на рис. 13.9.11.
13.10. МАКРОИНСТРУКЦИЯ ДЛЯ СДВИГА БИТОВ
Читателю, без сомнения, знакома инструкция PDP-11 ASL (Arithmetic
Shift Left — арифметический сдвиг влево) , а возможно, он уже и использо-
вал ее. Действие этой инструкции состоит в сдвиге каждого бита приемника
на одну позицию влево. Самый старший бит (бит 15) сдвигается в бит пере-
носа, а в освободившийся самый младший бит помещается 0. Иногда при
программировании бывает необходимо сдвигать биты слова влево не только
на один бит, но на несколько , например на 5 или на 8 бит. Конечно, для до-
стижения этого можно просто включить достаточное число инструкций ASL,
но если таких сдвигов нужно делать много, то вероятнее всего для выполне-
ния этой работы мы напишем макроинструкцию. Предлагаем пример на рис.
13.10.1.
297
.LIST ME
2
3 .MACRO SHIFTLEFT TARGET,COUNT,?ADR
4 MOV «COUNT,R0
5 ADR: ASL TARGET
6 SOB R0,ADR
7 .ENDM
8
9 000000 TART: SHIFTLEFT ADDR,4
000000 012700 MOV «4, R0
000004
000004 006367 64»: ASL ADDR
001002
000010 077003 SOB R0,64*
59 000012 133573 ADDR: .WORD 133573
Рис. 13.10.1
Здесь TARGET - это адрес 16-битового слова, подлежащего сдвигу, а
COUNT — это счетчик сдвига. Этот счетчик помещается в регистр R0, кото-
рый затем применяется для управления циклом в инструкции SOB, причем
телом этого цикла является просто сдвиг влево TARGET. Обратите внимание
на использование автоматически генерируемого локального символа ?ADR.
Здесь есть две потенциальных проблемы. Во-первых, R0 является счетчиком
для управления циклом, но, возможно, что этот регистр уже используется
для какой-либо другой цели, когда макроинструкция вызывается. Во-вто-
рых, возможно, что по невнимательности макроинструкции может быть пе-
редан неположительный счетчик, в результате чего произойдет, вероятно, не
то, на что программист надеется. Обе эти проблемы не так уж трудно разре-
шить, но мы отложим исправления до разд. 13.12. Однако здесь есть еще од-
на замаскированная трудность. Мы отложим ее обсуждение до разд. 13.13.
В приложении А содержится инструкция ЭВМ ГОР-11, называемая ASH
(Arithmetic SHift — арифметический сдвиг), которая делает больше, чем на-
ша макроинструкция SHIFTLEFT: она сдвигает влево или вправо в соответ-
ствии с заданным счетчиком сдвига. Тем не менее SHIFTLEFT служит двум
целям. Во-первых, она полезна как иллюстрация одной из многочисленных
особенностей ассемблера и как таковая будет основным примером в остав-
шейся части этой главы. Во-вторых, инструкция ASH реализована не на всех
моделях, и поэтому SHIFTLEFT или что-то подобное может оказаться дей-
ствительно практически полезным. В одном из упражнений читателю предла-
гается модифицировать эту макроинструкцию, чтобы дать более реалисти-
ческую эмуляцию инструкции ASH.
13.11. УСЛОВНО АССЕМБЛИРУЕМЫЕ ИНСТРУКЦИИ
Рассмотрим в качестве примера две исходные программы, реализующие
близкие задачи: одна размещает числа в возрастающем порядке; другая — в
порядке уменьшения. Предположим, что эти работы взаимосвязаны настоль-
ко тесно, что исходные программы отличаются лишь несколькими инструк-
циями, а большая часть обеих программ идентична. Чтобы их выполнить,
каждую программу, конечно, нужно ассемблировать, но мы могли бы изба-
298
виться от-написания двух версий, вероятно, длинных процедур, различающих-
ся лишь небольшим числом инструкций. Наша цель в этом разделе состоит в
разработке средств, позволяющих писать только одну версию программы,
содержащую некоторые вариантные инструкции, и затем информировать на-
шего служащего (или ассемблер PDP-11), какие варианты нужно использо-
вать при ассемблировании двух версий программы. Таким образом, основ-
ную часть инструкций потребуется написать только один раз, а служащий вы-
борочно отассемблирует или один набор вариантных инструкций, или другой.
Читатель может заявить, что нас одолевает леность, но мы предпочитаем счи-
тать это повышением эффективности работы программиста.
В основном примере гл. 6 рассматривалось нахождение максимума из на-
бора чисел, а в исходном коде программы, которая, в конце концов, выпол-
няла эту работу, содержалась инструкция BGE NEXT. Эта инструкция пред-
назначалась для обхода инструкции, которая заменяла временный максимум.
Когда мы заинтересовались написанием программы для нахождения мини-
мума из набора чисел, то нам пришлось сделать в программа единственное
изменение, заменив BGE NEXT на BLE NEXT. Нам бы хотелось иметь воз-
можность написать код для этой программы (программ?) один раз и затем
информировать служащего, какую версию мы хотим ассемблировать. При
последующем обсуждении будет полезно обратиться к листингу программы
на рис. 6.6.1.
Конечно, служащий должен быть в состоянии выборочно ассемблировать
исходный код при условии, что мы информируем его о том, какой код пред-
полагается ассемблировать, и об условиях, при которых он будет или не бу-
дет ассемблироваться. Мы хотим, чтобы служащий осуществлял выбор сре-
ди строк BGE NEXT и BLE NEXT, но какое условие сообщит ему о том, ка-
кой код нужно вставить и ассемблировать? Простая, но эффективная схема
состоит в том, что если символ FLAG (или любой другой символ, не исполь-
зуемый в программе) определяется в программе, то нужно ассемблировать
BGE NEXT. Но если FLAG в программе не определяется, то вместо этого
нужно ассемблировать BLE NEXT. Теперь мы можем выбрать, какую версию
хотим ассемблировать, определив символ FLAG где-то в начале программы
(что может быть сделано оператором прямого присваивания FLAG=0) или
оставив FLAG неопределенным. Обратите внимание, что символ FLAG не
служит в программе никаким другим целям, кроме того, что сообщает наше-
му служащему, какую инструкцию ветвления нужно ассемблировать. Исход-
ная программа, предоставляемая служащему, может быть теперь записана
следующим образом:
(FLAG = 0)
CMP R5, (R2)
* * Если символ FLAG определен, то ассемблиро-
. вать следующую инструкцию:
BGE NEXT
* * Если символ FLAG не определен, то ассембли-
ровать следующую инструкцию:
299
BLE NEXT
MOV (R.2),R5
NEXT: DEC R0
Таким образом, если первая строка (FLAG =0) включается в исходную про-
грамму, то служащий поместит этот символ в свою таблицу символов и,
когда встретит в. исходной программе операторы ** Если, то эта таблица
скажет ему, что FLAG был определен и, следовательно, здесь нужно ассемб-
лировать BGE NEXT. В противном случае, если мы оставим FLAG неопреде-
ленным, он будет ассемблировать BLE NEXT.
Аналогично и ассемблер ЭВМ PDP-11 может справляться с таким услов-
ным ассемблированием. Общий формат условного блока такой:
.IF условие аргумент (ы)
(Строка или строки
исходного кода, который
должен ассемблироваться,
когда проверка условия
дает значение ’’истина”)
.ENDC
Обратите внимание, что, поскольку в условный блок может включаться боль-
ше одной строки исходного кода, мы должны информировать ассемблер об
окончании условного блока директивой .ENDC (END Conditional block).
Только что описанная программа может быть теперь представлена ассембле-
ру, и мы видим, каким получается результат ассемблирования, если символ
FLAG определяется (рис. 13.11.1). Если же, с другой стороны, мы не смогли
определить FLAG в программе, то результат окажется таким, как показано
на рис. 13.11.2. Из первого программного сегмента видно, что, поскольку
FLAG был определен, была ассемблирована инструкция BGE NEXT, а ин-
струкция BLE NEXT не была ассемблирована. Во втором случае ситуация
как раз обратная, поскольку FLAG не был определен.
Для условных фраз "определен” (defined) и ”не определен” (not defined)
ассемблер ЭВМ PDP-11 использует соответственно аббревиатуры DF и NDF.
1 000000 FLAG • 0
7 000016 8 020512 002001 СМР .IF В6Е .ENDC R5»(R2) DF NEXT FLAG
9 10 000020
И • IF NDF FLAG
12 BLE NEXT
13 .ENDC
14 000022 011205 NOV (R2),R5
15 000024 005300 NEXT: DEC R0
РИС. 13.11.1
300
6 000016 020512 СМР R5HR2)
7 .IF DF FLAG
8 BGE NEXT
9 .ENDC
10 .IF NDF FLAG
11 000020 003401 BLE NEXT
12 .ENDC
13 000022 011205 MOV <R2bR5
14 000024 005300 NEXT: DEC R0
Рис. 13.11.2
Их называют дополняющими условиями. Есть еще целый ряд других усло-
вий, используемых ассемблером PDP-11. Информация о каждом из них дана
в табл. 13.11.3.
Таблица 13.11.3
Условия ассемблера PDP-11
Условие Название Аргумент (ы) Блок ассемблируется, если
EQ NE GT LE LT GE DF NDF В NB IDN DIF Равно 0 He равно 0 Больше 0 Больше или равно 0 Меньше 0 Больше или равно 0 Определен Не определен Пустой Не пустой Идентичные Различные Значение * j Символ | Строка Строка, Строка, Строка, Строка, Значение = 0 Значение ¥= 0 Значение > 0 Значение <0 Значение <0 Значение > 0 Символ определен Символ не определен Аргумент — пустая строка Аргумент — не пустая строка Строка, идентична строке, Строка, отлична от строки,
* Под значением мы понимаем любое выражение, которому ассемблер может при-
своить числовое значение. Таким образом, такие выражения как ADDR7, START— ADDR,
<START — ADDR + 2>/2 +14, — это все значения, тогда как R3, PC + 4 и т. п. — иет.
Даже в самом лучшем случае написание условно ассемблируемых бло-
ков — дело довольно хитрое, требующее большого внимания, если мы хотим,
чтобы результаты получались такими, как ожидаются. Необходимо помнить
о двух вещах. Во-первых, программист должен точно знать, что ассемблер
проверяет, когда он обрабатывает условную директиву, и, во-вторых, про-
граммист должен быть осведомлен о том, какая именно информация доступ-
на ассемблеру. Простой пример проиллюстрирует некоторые ловушки, уго-
тованные для новичков.
Предположим, что ADDR — это определенный пользователем символ, ис-
пользуемый в качестве метки для ячейки памяти с адресом 000622. Рассмот-
рим последовательность инструкций
301
/ CLR ADDR
.IF EQ ADDR
(условный блок)
£NDC
Начинающий может изумиться, найдя, что условный блок не был ассемблиро*
ван, хотя ясно, что ADDR в нем имеет значение 0. Здесь допущены две глав-
ных ошибки или, по крайней мере, два неверных толкования. Во-первых,
программист предполагал, что проверке подлежало содержимое ячейки
ADDR, а не значение символа ADDR. В действительности, значение ADDR есть
000622, что, конечно же, не 0. Во-вторых, даже если оставить в стороне пер-
вую ошибку, программист посчитал содержимое ADDR равным 0 (посколь-
ку его содержимое было очищено) , но он (или она) проглядел тот факт, что
эта очистка содержимого ADDR имеет место во время выполнения програм-
мы, а не во время ассемблирования. Ассемблер ничего не знает о том, что
происходит, когда программа выполняется, да и вообще о содержимом раз-
личных ячеек памяти ему ничего не известно.
В оставшейся части главы мы проиллюстрируем условное ассемблирова-
ние рядом примеров, используя различные условные директивы. В каждом
случае мы дадим подробные объяснения того, как ассемблер с ними обхо-
дится. Пока же предлагаем несколько простых примеров некоторых особен-
ностей ассемблера и того, что может происходить неправильно. Рассмотрим
сначала следующую простую ситуацию. Символу ADDR было назначено зна-
чение 0 в операторе прямого присваивания, и затем условная директива про-
веряет ADDR, чтобы увидеть, не равен ли он 0. Проверка дает истинное значе-
ние и, как следовало ожидать, условный блок ассемблируется (рис. 13.11.4).
В программном сегменте на рис. 13.11.5 символу ADDR также присваива-
ется значение 0, но на этот раз условная директива помечается флагом ошиб-
ки. Опять символ ADDR получил значение 0, поскольку это метка ячейки па-
мяти 000000, так что условная директива .IF EQ ADDR должна в результа-
те проверки давать значение ’’истина”. В действительности это так и есть, и
мы видим, что инструкция CLR R3 была ассемблирована. Проблема заключа-
ется в том, как символу ADDR было дано его значение. В этом втором слу-
чае ADDR является меткой, имеющей значение 0, поскольку она была ас-
семблирована в ячейке 000000. Ho ADDR — это символ, чувствительный к
1 000000 ADDR » 0
53 • .IF EQ ADDR
54 001012 55 Рис. 13.11.4 005003 CLR .ENDC R3
1 000000 000402 ADDRi .WORD 402
А 36 .IF EQ ABDR
37 001014 38 Рис. 13.11.5 005003 CLR .ENDC R3
302
22 000024 00040? ADDR: ^UORD 402
44 001040 START: 2 IF GT START-ADDR
45 001040 005003 CLR R3
46 .ENDC
Рис. 13.11.6
1 000000 START: .IF GE START-ADDR
2 CLR R3
3 .ENDC
Р 4 000000 010346 ADDR: NOV R3»-<SP)
5 000001 .END
ТАБЛИЦА СИМВОЛОВ
ADDR 000002R START OOOOOOR
Рис. 13.11.7
перемещению: если бы эта программа была перемещена на какую-то другую
ячейку памяти (например, 001000), то значение ADDR не было бы больше
нулевым, и, следовательно, условный блок не должен был ассемблировать-
ся. Таким образом, эта ошибка может восприниматься как предостереже-
ние — ассемблирование этого условного блока будет правильным, только ес-
ли программа загружается так, что ADDR присваивается значение О1. В пер-
вом случае такой проблемы не существует, поскольку прямое присваивание
ADDR = 0 дает символу ADDR абсолютное значение 0, которое к перемеще-
нию программы не чувствительно.
Разрешив этот вопрос и получив некоторое представление о том, как и по-
чему ассемблер обходится с подобными конструкциями, читатель может
быть озадачен программным сегментом на рис. 13.11.6. Очевидно, что START
и ADDR — это символы, значения которых зависят от адреса загрузки про-
граммы, как в предыдущем примере, и все же кажется, что ассемблер не
обеспокоен этой условной конструкцией. Однако обратите внимание, что, хо-
тя оба символа чувствительны к перемещению, их разность START — ADDR =
= 001040 — 000024 = 001014 — не чувствительна. Эта разность равна всегда
001014, независимо от того, где в оперативной памяти программа будет раз-
мещена. По этой причине ассемблер смог достоверно установить, что START—
ADDR больше, чем 0, и, таким образом, ассемблировал условный блок.
В качестве последнего примера рассмотрим последствия того, что происхо-
дит, когда ссылка на символ в условной директиве производится до его
определения (рис. 13.11.7). (Этот пример должен напоминать программный
сегмент из разд. 13.4, в котором фазовая ошибка в существенной степени
была порождена той же самой причиной. Из таблицы символов видно, что
значение ADDR есть 000002, тогда как значение START есть 000000. Посколь-
ку разность START — ADDR отрицательная, условный блок (который зави-
сит от условия GE) не должен ассемблироваться, действительно, этого сдела-
1 Здесь имеется в виду присвоение значения во время ассемблирования; любое пере-
мещение загрузочного модуля никакого влияния на условное ассемблирование оказать
уже не в состоянии. - Прим, перев.
303
но не было. Но опять мы доляшы внимательно проследить обработку ассемб-
лером условной директивы. Когда ассемблер впервые встретил конструк-
цию .IF на первом проходе по исходному коду, он не располагал значением
для ADDR. Поэтому он рассматривал запись START — ADDR, как если бы
был просто символ START, значение которого (0) было больше или равно
0. Следовательно, на первом проходе ассемблер выяснил, что код инструк-
ции CLR R3 должен быть вставлен здесь на втором проходе, и поэтому уве-
личил свой счетчик распределения ячеек на 2. Таким образом, когда было
встречено определение ADDR, счетчик распределения ячеек уже был продви-
нут до 0000002, что и объясняет значение ADDR в таблице символов. Но на
своем втором проходе ассемблер нашел, что разность START — ADDR не бы-
ла больше или равна 0, и поэтому не ассемблировал инструкцию CLR R3. Ко-
гда опять было встречено определение ADDR: счетчик распределения ячеек
имел значение 000000, что не согласовывалось со значением ADDR из табли-
ци символов. Это и вызвало фазовую ошибку.
Как и прежде, мы можем лишь увещевать программиста, что он должен по-
заботиться о том, чтобы символы хорошо определялись до того, как они ис-
пользуются. Доскональное понимание того, как ассемблер обрабатывает эти
различные конструкции, позволит устранить подавляющее большинство оши-
бок программирования. И все же лучший учитель — это опыт.
13.12. ДИРЕКТИВА АССЕМБЛЕРА .МЕХГГ
Когда в ходе расширения макроинструкции ассемблер встречает директи-
ву .МЕХГГ, он останавливает расширение макроинструкции, т. е. ведет себя
так, как если бы встретил директиву .ENDM. Поэтому, например, при расши-
рении макрбинструкции
.MACRO PUSH А, В
MOV А, —(SP)
.МЕХГГ
MOV B,-(SP)
.ENDM
вызванного с помощью PUSH Rl, R2, была бы ассемблирована только ин-
струкция
MOV Rl, —(SP)
Инструкция MOV R2, — (SP) в листинге была бы показана, но из-за директи-
вы .MEXIT она никогда не была бы ассемблирована. Конечно, писать подоб-
ную макроинструкцию было бы бессмыслицей. Принципиальное использо-
вание директивы .МЕХГГ состоит в остановке расширения макроинструк-
ции, ассемблируемой условно. То есть мы хотим, чтобы при некоторых об-
стоятельствах ассемблер остановил процесс ассемблирования и продолжал
свою работу после макрорасширения. В других случаях мы хотим, чтобы ас-
семблирование продолжалось. В качестве примера использования этой дирек-
тивы предлагаем улучшенную версию макроинструкции, которую мы разра-
ботали ранее в этой главе.
Вспоминаем, что в разд. 13.9 мы определили макроинструкцию по имени
SAVE, содержащую блок с неопределенным числом повторений, назначени-
ем которой было сохранение в стеке содержимого четырех регистров.
304
.MACRO SAVE A,B,C,
.IRP X, <A,B,C,D>
MOV R’X, —(SP)
.ENDM
JENDM
Список формальных аргументов в операторе прототипа макроса позволял
нам указывать, содержимое каких регистров мы хотели бы поместить в стек.
Но все же эта макроинструкция не настолько полезна, как могла бы быть,
так как из определения видно, что она может использоваться для сохранения
содержимого только четырех регистров, никогда больше и никогда меньше.
Однако при условном использовании директивы .МЕХГГ мы можем модифи-
цировать этот пример, чтобы он стал, насколько это возможно, общецеле-
вым. Эта модификация показана на рис. 13.12.1, где мы полагаем, что нико-
гда не потребуется сохранять в стеке содержимое больше шести регистров.
Изменения производятся в строках 3 и 4, где от макроинструкции блоку
повторения передаются шесть аргументов, и в строках 5, 6 и 7,где мы вста-
вили условный блок: выход из макро инструкции, если аргумент X пустой.
Будет полезно проследить, что происходит, когда эта макроинструкция вы-
зывается. Следует помнить, что блок повторения будет расширяться шести-
кратно каждый раз, когда вызывается макроинструкция SAVE, поскольку
блоку повторения всегда передаются шесть аргументов.
1 .LIST — ME
2 3 4. 5 6 7 8 ? 10 11 12 000000 STARTs J .MACRO .IRP .IF .MEXIT .ENDC MOV .ENDM .ENDM SAVE SAVE C'D'ErFrGrH Xr<CrD*ErF«GrH> D X R’Xt-(SP) 1»2»4
13 000000 000002 000004 000006 010146 010246 010446 .IRP .IF •MEXIT .ENDC MOV .ENDM .IF .MEXIT .ENDC MOV • IF .MEXIT .ENDC NOV .IF .MEXIT .ENDC MOV .IF .MEXIT .ENDC NOV X»(l»2,4»»»> D X R’Xt-(SP) В 1 R1»-(SP> В 2 R2*-<SP) В 4 R4»-(SP) В R»-(SP) J(ЯР0Д0Л1ЕНМЕ J ПРОГРАММЫ)
Рис. 13.12.1
305
Когда блок повторения расширяется первый раз, условная директива вы-
глядит так: /
.IF В 1
Поскольку цепочка 1 не пуста, тело условного блока’, а именно, .МЕХГГ, не
срабатывает, и инструкция
MOVRl.-(SP)
ассемблируется. При следующих двух расширениях блока повторения ассемб-
лер предпринимает аналогичные действия, поскольку ни одна из цепочек 2
или 4 не пуста. Однако при четвертом расширении блока повторения фор-
мальный аргумент X заменяется пустой цепочкой ” ”, и проверка условий
становится такой:
.IF В” ”
(Мы здесь вставили символ ” ”, чтобы показать, что проверяется пустая це-
почка.) В этом случае, хотя ассемблер показывает в листинге расширение,
а именно,
MOV R, — (SP)
он не ассемблирует его. Обратите внимание, что он правильно взял формаль-
ный аргумент X как пустую цепочку и выполнил конкатенацию его с симво-
лом R. Аналогично два оставшихся расширения блока повторения показа-
ны в листинге, но не ассемблированы. Таким образом, мы сконструировали
весьма и весьма полезную ’’суперинструкци^о” для сохранения содержимого
такого числа регистров, какое нам нужно, именно условный блок освободил
нас от ответственности за подсчет аргументов. Читатель может счесть, что по-
лезно построить парную макроинструкцию, например RESTORE, для вытал-
кивания регистров из стека.
1 .LIST ME
2 3 4 5 6 7 в 9 10 11 12 13 14 000000 1 .MACRO SHIFTLEFT TARGET»COURT»?AHR .IF LE COUNT .«EXIT ENDC MOV RO»-(SP) MOV «COUNT»R0 ADRi ASL TARGET SOB R0»ADR MOV (SF)+,RO .ENDM START: SHIFTLEFT ADDR»4
000000 000002 000006 .IF .MEXIT ENDC 010046 MOV 012700 NOV 000004 006367 64*: ASL LE 4 RO.-(SP) 04» R0 ADDR
000012 000014 65 001016 001004 077003 SOB 012600 NOV 133573 ADDR: 133573 R0»64* <SP)+»RO
Рис. 13.12.2
306
1 2 .LIST
3 .MACRO SHIFTLEFT TARGET,COUNT,?ADR
4 .IF LE COUNT
5 .MEXIT
6 .ENDC
7 NOV RO,-(SP>
8 MOV ‘COUNT,R0
9 ADR: ASL TARGET
10 SOB R0,ADR
11 MOV (SP)+,RO
12 .ENDM
13
14 000000 START: SHIFTLEFT ADDR,-4
IF LE -4
.MEXIT
.ENDC
MOV R0,-(SI’>
MOV ‘-4,R0
64»: ASL ADDR
SOB R0,64»:
MOV (SP)+,RO
65 001016 133573 ADDR: 133573
Рис. 13.12.3
Вернемся теперь к макроинструкции SHIFTLEFT из разд. 13.10. Вспоми-
наем, что мы обнаружили две потенциальные проблемы, одна из которых со-
стояла в возможной изменчивости содержимого ’’считающего” регистра, R0,
а другая касалась возможности передачи неположительного счетчика сдви-
гов. Теперь мы довольно легко можем справиться с обеими этими пробле-
мами (рис. 13.12.2). Первая решается просто проталкиванием содержимого
R0 в стек перед его использованием и восстановлением его позднее. Для ре-
шения второй мы вставили условный блок, который осуществляет выход
из макроинструкции, если счетчик неположителен. Хотя эта последняя вер-
сия листинга совсем проста, читатель может извлечь определенную пользу из
ее изучения.
Макроинструкция, вызванная в строке 14, была расширена, как и ожида-
лось, поскольку счетчик сдвигов (4) был не меньше или равен 0 (LE). На
рис. 13.12.3 показан еще один листинг этого макровызова, причем на этот
раз счетчик сдвигов неположительный. Как видим, ассемблер не сгенериро-
вал никакого мащинного кода[.
13.13. ПОДУСЛОВИЯ
В разд. 13.11 мы ввели понятие условного ассемблирования, когда выбо-
рочно ассемблировали BGE NEXT или BLE NEXT в соответствии с тем, был
илй не был определен символ FLAG. Хотя нужная нам работа делалась, само
условие было несколько неудобным:
Ассемблировать BGE NEXT, если FLAG определен;
ассемблировать BLE NEXT, если FLAG не определен.
Листинг программного сегмента на рис. 13.11.1 показывает, что для обработ-
ки этой конструкции потребовались два последовательных условных блока.
307
Более естественным (по крайней мере, с точки зрения русского языка)
представлялось бы: /
Ассемблировать BGE NEXT, если FLAG определен,
иначе ассемблировать BLE NEXT.
Другими словами, если проверка условия лает истинное значение, то надо де-
лать одно, но если оно ложное, то делать что-то иное. Такие альтернативные
конструкции ассемблер ЭВМ PDP-11 может генерировать с помощью под-
условий.
Подусловие — это директива вида .IFT (IF True — если истинно) , .IFF (IF
False — если ложно), или .IFTF (IF True or False — т. e. при любых обстоя-
тельствах) . Эти директивы не несут никаких аргументов, и так как сам опе-
ратор подусловия не ассемблируется, то он не должен помечаться меткой, по-
скольку такая метка не будет рассматриваться как программный символ.
Подусловие делит условный блок на подблоки следующим образом. Под-
блок начинается любой директивой условия или подусловия и заканчивается
любой другой директивой подусловия или директивой .ENDC, означающей
конец условного блока. Если проверка условия дает истинное значение, то
код в самом подблоке условия, а также код в любом подблоке .IFT или
.IFTF будет ассемблироваться. Если проверка условия дает ложное значение,
то будет ассемблироваться код в любых подблоках .IFF или .IFTF. Следую-
щая диаграмма иллюстрирует реакцию ассемблера на условия и подусловия:
.IF условие (ассемблирует, если условие истинно)
.IFF (ассемблирует, если условие ложно)
.IFTF (ассемблирует, если условие
истинно или ложно)
.IFT (ассемблирует, если условие истинно)
*ENDC
Теперь довольно легко можно использовать преимущества этих под-
условий для последнего исправления макроинструкции SHIFTLEFT. Что-
бы увидеть, где скрывается проблема, предположим, что мы вызываем
SHIFTLEFT так, что аргумент, передаваемый на месте формального аргумен-
та TARGET, есть R0, Регистр R0 теперь используется и как слово, подлежа-
щее сдвигу, и как счетчик сдвигов, и из листинга на рис. 13.13.1 видно, что
ситуация получается плохой.
Хотя обойти эту трудность можно целым рядом способов, общий подход
должен быть ясен. Если слово, подлежащее сдвигу, не есть R0, то R0 может
безопасно использоваться в качестве счетчика сдвигов. Но если подлежащее
сдвигу слово есть R0, то, чтобы избежать конфликта, который мы видим в
листинге, для счетчика сдвигов следует использовать какой-то другой ре-
гистр, например R1. При этом подходе подразумевается проверка условия,
308
1 о .LIST ME
3 .MACRO SHIFTLEFT TARGET.COUNT>?ADR
4 .IF LE COUNT
.MEXIT
6 .ENDC
7 MOV RO.-CSP)
8 MOV SCOUNT.RO
Л ADR: ASL Target
10 SOB R0.ADR
11 MOV (SPl+.RO
12 .ENDM
54 001226 SHIFTLEFT R0.4
.IF LE
.MEXIT
.ENDC
001226 010046 MOV RO.-CSP)
001230 012700 MOV #4» R0
000004
001234 006300 64*: ASL R0
001236 077002 SOB R0.64*
001240 012600 MOV <SP)+,RO
Рис. 13.13.1
не является ли фактический аргумент, заменяющий TARGET, регистром R0.
С этой проблемой мы справимся, определив вторую макроинструкцию, SL
(рис. 13.13.2). Действие в основном разыгрывается в SL, a SHIFTLEFT ис-
пользуется теперь только в качестве макроинструкции, предваряющей SL.
Макроинструкция SHIFTLEFT просто решает, какой аргумент должен
пересылаться макроинструкции SL, и затем вызывает SL. Как видно из
листинга, SHIFTLEFT проверяет счетчик сдвигов и, если он неположитель-
ный, производит выход. Затем она проверяет, не является ли сдвигаемое
слово, представляемое формальным аргументом TARGET регистром R0.
Если это так, то она вызывает макроинструкцию SL с аргументами TARGET,
COUNT и 1 , где 1 показывает, что SL должна использовать в качестве регист-
ра-счетчика R1 (вместо R0). Если R0 не является словом, подлежащим сдви-
гу, то SHIFTLEFT может безопасно позволить макроинструкции SL исполь-
зовать R0 в качестве регистра-счетчика. Для этого она вызывает макроин-
струкцию SL с аргументами TARGET, COUNT, 0. Теперь макроинструкция
SL делает в существенной степени то, что делала ранняя' версия макроин-
струкции SHIFTLEFT, — она выполняет фактический сдвиг целевого слова.
Мы опять рекомендуем читателю внимательно ознакомиться с определения-
ми этих двух макроинструкций и их расширениями.
Предлагаем окончательную версию макроинструкции SHIFTLEFT (рис.
13.13.3). В этом случае макроинструкция SL определяется внутри определе-
ния макроинструкции SHIFTLEFT и это показывает, что определения макро-
йнструкций могут быть вложенными. Это более чем любопытно, поскольку
строки 17 и 19 листинга показывают, что SHIFTLEFT способна вызывать
макроинструкцию SL, просто передавая ей номер регистра, без необходи-
мости передавать аргументы TARGET и COUNT .Причиной этого является то,
что всякий раз, когда макроинструкция В определяется внутри определения
309
1 • LIST ME 14 .ENDM SL
2 15
3 .MACRO SHIFTLEFT TARGET,COUNT 16 .IF IDN TARGET,R0
4 .IF LE COUNT 17 SL 1
5 .MEXIT 18 .IFF
6 .ENDC 19 SL 0
7 .IF IDN TAR6ET.R0 20 .ENDC
8 SL TARGET,COUNT.1 21 .ENDM SHIFTLEFT
9 .IFF •
10 SL TARGET,COUNT.0
И .ENDC
12 .ENDM 63 001226 SHIFTLEFT R3.7
13 .IF LE 7
14 .MACRO SL A,B,C,?D .MEXIT
15 MOV R’C.-(SP) .ENDC
16 MOV *B,R'C S
17 И: ASL A .MACRO SL C,?D
18 SOB R’C.D MOV R’C,-(SP>
19 MOV (SP)+.R’C MOV ♦7,R’C
20 .ENDM Ds ASL R3
• SOB R’C.D
- MOV (SP)+,R’C
.ENDM SL
45 001226 SHIFTLEFT R3.7
.IF LE 7 .IF IBN R3,R0
.MEXIT SL 1
.ENDC .IFF
.IF IDN R3.R0 001226 SL 0
SL R3.7.1 001226 010046 NOV RO.-(SP)
• IFF 001230 012700 NOV •7,RO
001226 SL R3.7.0 000007
001226 010046 NOV RO.-ISP) 001234 006303 64»: ASL R3
001230 012700 MOV <7. RO 001236 077002 SOB RO,64»
>01234 000007 64»: 001240 012600 MOV (SPH.R0
006303 ASL R3 .ENDC
001236 077002 SOB RO.64»
001240 012600 MOV (SP)+,RO
.ENDC
• 89 002262 SHIFTLEFT RO,2
- .IF LE 2
.MEXIT
73 002262 SHIFTLEFT RO.2 .ENDC
.IF LE 2
.MEXIT .MACRO SL C,?D
.ENDC IDN RO.RO RO.2.1 Rl.-(SP) 62,Rl MOV R’C.-(SP)
002262 002262 002264 010146 012701 .IF SL MOV MOV Ds MOV ASL SOB ♦2.R’C RO R’C.D
000002 MOV (SPl+.R’C
002270 006300 65»: ASL RO .ENDM SL
002272 077102 SOB R1.65» r IDN RO,RO
002274 012601 MOV (SP)+,R1 . IF
• IFF RO,2,0 002262 SL 1
SL 002262 010146 MOV Rl.-(SP)
.ENDC 002264 012701 MOV 62,Rl
. 000002
• 002270 006300 65»: ASL RO
- 002272 077102 SOB Rl,65*
002274 012601 MOV (SP)+,R1
Рис. 13.13.2 .IFF SL
.ENDC
1 .LIST HE
2 •
3 .MACRO SHIFTLEFT TARGET,COUNT
4 .IF .MEXIT LE COUNT Рис. 13.13.3
6 7 .ENDC 1 макроинструкции A , любые формальные аргументы (и следовательно, при
8 9 .MACRO MOV r’c -<sp> ° вызове макроинструкций А - любые фактические аргументы) макроин-
10 MOV ♦count,r’c струкции А становятся также аргументами и внутренней макроинструкции В.
И 12 D: ASL SOB TARGET « R,C,B Такое вложение определении в некоторых случаях может существенно
13 MOV <8F>+’R’C упростить вызов одной макроинструкции другой.
311
310
13.14. НЕКОТОРЫЕ ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ
Читатель, без сомнения, заметил, что листинги программных сегментов
в этой главе, особенно в нескольких последних разделах, были излишне
длинными. Такие длинные листинги появились вследствие того, что если
ассемблеру предписывается показывать в листинге макрорасширения, то он
показывает определение самой макроинструкции каждый раз, когда она вы-
зывается, а также показывает любые условные блоки независимо от того,
ассемблируются они или нет. Такие листинги могут быть весьма и весьма по-
лезны в процессе разработки программного модуля, но, как только модуль
отлажен и проверен и начинается разработка следующего модуля, програм-
мист может предпочесть не видеть расширений макроинструкций в листинге
первого моделя. Как читатель уже знает, показ расширений макроинструк-
ций можно выборочно разрешить или запретить с помощью директив ассемб-
лера .LIST ME и JNLIST ME, которые могут быть включены в любое место
программы. Аналогично можно разрешить или запретить листинг неудовлет-
воренных условий с помощью директив .LIST CND и .NLIST CND (LIST or
do not LIST CoNDitionals). Даже когда действует .NLIST CND, те инструк-
ции в условных директивах, которые ассемблируются, будут в листинге по-
казаны.
В этой главе мы познакомились с многочисленными особенностями ас-
семблера ЭВМ PDP-11, способного обрабатывать макроинструкции и услов-
ные блоки. Ассемблер обладает и некоторыми другими возможностями, ко-
торые мы не обсуждали. Наш выбор мы основывали на максимальной полез-
ности для программиста, а для ознакомления с другими особенностями ас-
семблера, которые могут оказаться полезными, читателю рекомендуется изу-
чить соответствующие руководства1. Данная глава была посвящена подроб-
ностям некоторых из этих концепций, но то разнообразие способов, какими
можно писать макроинструкции и условные блоки, не оставило нам никакой
другой возможности, как рассмотреть небольшие примера образцов програм-
мных сегментов. Добросовестный программист сможет достичь ясного пони-
мания этих идей и свободы обращения с ними путем их использования, т. е.
написания программных сегментов с неизбежными пробами и ошибками.
Наконец, мы можем заключить, что, хотя как макроинструкции, так и
подпрограммы могут выполнять много раз какую-то задачу, в действитель-
ности это различные концепции. Макроинструкция обладает значительной по-
тенциальной гибкостью в отношении того, как она вызывается, а значит, и
какие коды генерирует. К недостаткам ее относится то, что при каждом вы-
зове макро инструкции может заниматься изрядный объем памяти, посколь-
ку расширение кодируется непосредственно в программе. Обращения к под-
программе обладают меньшей гибкостью, но экономят память. Опытный
программист сможет сбалансировать гибкость макроинструкций со свой-
ством подпрограмм экономить память и будет принимать подходящие реше-
ния, основываясь на полной задаче, подлежащей выполнению.
1 См. примечание на с.286.~ Прим, пере в.
13.15. УПРАЖНЕНИЯ
13.1.1. Напишите программу для поискав текстовой строке первого вхождения за-
данной целевой цепочки и для возврата позиции целевой цепочки, если она найдена; в
противном случае должен возвращаться 0. При возврате к вызывающей программе най-
денная позиция должна быть оставлена в стеке. Предполагается, что как текстовая стро-
ка, так и целевая цепочка, оканчиваются знаком NUL. Таким образом, например, если
текстовая строка есть #SAMPLE,LP:=MT:(NUL), а целевая цепочка есть LP:(NUL), то
подпрограмма должна вернуть в стеке число 9. Пусть R0 содержит адрес первого байта
целевой цепочки, a R1 — адрес первого байта текстовой строки.
13.1.2. Покажите, что подпрограмма SAVE на рис. 13.1.3 будет выполняться правиль-
но, если ее записать так:
SAVE: MOV (SP)+ , RETURN + 2
MOV Rl, -(SP)
MOV R3,-(SP)
MOV R5, -(SP)
RETURN: JMP @#0
Будут ли достигнуты правильные результаты при
RETURN:JMP 0
13.1.3. Покажите, что в программе на рис. 13.1.2 последовательность инструкций
CLC
BR DONE
EOT: SEC
DONE: RTS
может быть заменена на
TST (РС)+
ЕОТ: SEC
RTS
13.2.1. Предположим, что мы пишем две макроинструкции, SAVE и CLEAR, опреде-
ленные следующим образом:
Определение CLEAR
CLR Rl
CLR R3
CLR R5
Определение SAVE
MOV Rl, -(SP)
MOV R3,-(SP)
MOV R5, -(SP)
CLEAR
Как обработает служащий ссылку на SAVE? С какими проблемами (если таковые бу-
дут) он столкнется при расширении згой макроинструкции?
13.2.2. Если SAVE - имя макроинструкции, то как будет (и как должен) ассембли-
рующий служащий реагировать на следующие четыре конструкции?
a) SAVE б) .WORD SAVE
в) MOV R2, SAVE г) .ASCII /SAVE/
13.3.1. Запишите определения двух макроинструкций из упражнения 13.2.1 в форма-
те, приемлемом для ассемблера ЭВМ PDP-11. Поскольку макроинструкция SAVE содер-
жит в своем определении вызов макроинструкции CLEAR, должно ли определение
SAVE размещаться физически впереди определения CLEAR? Почему да или почему нет?
313
312
13.4.1. Если в программном сегменте на рис. 13.4.3 обращение к подпрограмме
SORT будет записано как
JSR PC, SORT
SAVE, 20
вместо
JSR PC, SORT
SAVE
20
то какой код сгенерирует ассемблер? (Мы просим здесь читателя дать грамотное пред-
положение. Решение этого вопроса было дано в разд. 13.5.)
13.4.2. Предположим, что нам часто необходимо увеличивать содержимое регистров
или ячеек памяти на 4, и поэтому для этой работы мы записываем макроинструкцию
.MACRO INCX
ADD #4, X
.ENDM
Выбрав для этой макроинструкции имя, совпадающее с одной из мнемоник ассемблера,
мы, без сомнения, создаем основу для возможных проблем. Программный сегмент на
рис. 13.15.1 показывает, что происходит при вызове этой макроинструкции.
Читатель может удивиться, обнаружив, что .ассемблер очевидно отдал предпочтение
макроинструкции перед мнемоникой. И хотя представляется, что все идет хорошо, из
рис. 13.15.2, где макроинструкция INC определяется после того, как вызывается, вид-
но, что могут возникать проблемы. В частности, дайте подробное объяснение, почему
метка LOOP: в строке 39 была помечена флагом фазовой ошибки.
13.4.3. Какой реакции следует ожидать от ассемблера на программу, содержащую
два определения макроинструкций с одним и тем же именем? Будет ли ассемблер обра-
батывать- их по-разному, если макроинструкции задают разное число аргументов? (Чи-
татель может попытаться построить пример этого и обнаружит довольно удивительный
ответ.)
13.5.1. Используя определение макроинструкции SAVE, данное на с. 280, покажите,
каким будет макрорасширение для каждого из следующих вызовов:
a) SAVE R0,R1,R1 б) SAVE @(R0)+ ,#2, #4
в) SAVE #’A,R1,RO г) SAVE 2,4,6
д) SAVE 1,2,3
13.5.2. Напишите макроопределение, которое будет вызывать с помощью JSR любую
подпрограмму при использовании любого регистра для перехода и передавать подпро-
грамме один аргумент в директиве .WORD, непосредственно следующий за инструкцией
J SR. Постройте макрорасширения для нескольких вариантов аргументов макроинструк-
ции.
13-5.3. Модифицируйте макроинструкцию из упражнения 13.5.2 для передачи подпро-
грамме аргумента в стеке, а не в директиве .WORD.
135.4. Рассмотрим макроинструкцию, определенную следующим образом:
.MACRO DUMMY А, В, С
С: MOV В
DEC А
BNE С
.ENDM
1 .LIST ME
2 1
3 .MACRO INC X
4 ADD 04rX
5 .ENDM
•
37 002102 INC R2
002102 062702 ADD •4» R2
000004
Как будет расширена эта макроинструкция для каждого из следующих вызовов?
a) DUMMY
б) DUMMY
в) DUMMY
г) DUMMY
д) DUMMY
е) DUMMY
ж) DUMMY
Rl, <R2, R3>, LOOP
Rl, R2, R3, LOOP
Rl, <R2, R3>
LOOP<@ (R1 )+,@ - (R1 )> .LOOP
R1„LOOP
,<R3, R4>,LOOP
,,<R3, R4>,LOOP
Рис. 13.15.1
1 .LIST ME
2 * •
24 001266 INC R2
001266 062702 000004 ADD •4» R2
P 39 001366 005005 LOOPS CLR R5
73 .MACRO INC X
7* ADD 04 »Х
75 .ENDM
1355. а) В макроинструкций из упражнения 13.5.4 мы передавали в качестве аргу-
мента макроинструкции метку С. Таким образом, если бы третий аргумент при вызове
макроинструкции DUMMY был LOOP, то С: было бы расширено как LOOP: Почему С:
распознается ассемблером как вхождение формальной переменной С, тогда как терм
RA (см. рис. 13.5.1) не рассматривался как содержащий вхождение А? (Читателю при-
дется здесь немного поразмышлять.)
б) Можем ли мы записать макроинструкцию как
.MACRO DUMMY А, В, С
С MOV В
DEC А
BNE С
.ENDM
и затем вызвать ее, например, с помощью DUMMY Rl, <R2, R3>, LOOP:?
в) Распознает ли ассемблер #Х как вхождение формальной переменной X (напри-
мер, в инструкции MOV #X,R0)?
13.5.6. Выполните еще раз упражнение 13.4.1.
13.5.7- Рассмотрим макроинструкцию, близкую макроинструкции CUSTOM на рис.
13.5.7, которая в данном случае вызывает другую макроинструкцию:
РИС. 13.15.2
314
315
.MACRO SPECIAL A
INSTR A
.ENDM
.MACRO INSTR X
X
.ENDM
Каким будет эффект от следующих вызовов?
a) SPECIAL MOV #3,R2
б) SPECIAL <MOV #3,R2>
в) SPECIAL < < MOV #3,R2>>
13.6.1. Мы видели, что макроинструкция SETCODE на рис. 13.6.2 потерпела неудачу
из-за того, что апостроф, который мы предназначали для того, чтобы ассемблер сгенери-
ровал код ASCII, был воспринят как символ конкатенации. Мы приняли тогда решение
переписать макроопределение. Как можно использовать эту макроинструкцию в том ви-
де, в каком она написана, чтобы поместить код ASCII какого-либо знака в регистр R0?
13.6.2. Как макроинструкция
.MACRO SAMPLE X,Y
.WORD ’X”Y’
.ENDM
будет расширена для каждого из следующих вызовов?
a) SAMPLE ADD, R б) SAMPLE ’ADD, R’
в) SAMPLE ADD’, R r) SAMPLE ADD’, ’R
13.6.3. Каким будет макро расширение для TEST А, В, если макроинструкция TEST
определяется как
.MACRO TEST X,Y
.WORD ’X’”Y’
.ENDM
13.6.4. Рассмотрим макроинструкцию, определенную следующим образом:
.MACRO SAVE P,Q,R
MOV R’P, —(SP)
MOV R’Q,-(SP)
MOV R’R, -(SP)
.ENDM
Каким будет расширение для SAVE 0,2, 3?
13.6-5. Каким будет расширение при вызове CODE #,Н, где макроинструкцйя CODE
определяется так:
.MACRO CODE SYM.CHAR
MOVB ’SYM ”’CHAR,R0
.ENDM
13.6.6. Рассмотрим макроинструкцию
.MACRO ZERO M
CLR RM’
.ENDM
Как будет расширено ZERO 2?
13.6.7. Напишите макроинструкцию для выталкивания содержимого трех регастров
из аппаратного стека.
13.6.8. Напишите макроинструкцию для выталкивания содержимого трех регистров
из стека, в качестве указателя которого используется четвертый регистр.
133. 1. Рассмотрим макроинструкцию, определенную так:
.MACRO MOVE, А,?В
MOV А,(В)
.ENDM
а) Каким будет расширение при вызове MOVE ^tl,R2?
б) Каким будет расширение при вызове MOVE (SP)+?
133. 2. Рассмотрим следующую модификацию макроинструкции BIGGER из рис.
13.8.3:
.MACRO BIGGER A,B,C,?D
MOV R’A.R’C
CMP R’A.R’B
BGE L’D
MOV R’B.R’C
L’D:
.ENDM
а) Какие метки будут генерироваться при первых двух вызовах макроинструкции
внутри одного и того же блока локальных символов?
б) Если макроинструкция вызывается в разных блоках локальных символов, то бу-
дут ли сгенерированы одинаковые метки? Если да, то будут ли они рассматриваться как
многократно определенные символы?
133. 3. Рассмотрим макроинструкцию, определенную так:
.MACRO LABELS А,?В,?С
В: CLR R’A
С: MOV #В, — (SP)
.ENDM
Какие значения будут даны В и С при первых двух вызовах макроинструкции LABELS
внутри одного и того же блока локальных символов?
13.9.1. Приведет ли к конфликту в блоке с неопределенным числом повторений
.IRP X,<W,X,Y, Z>
тот факт, что символ X используется дважды для разных целей?
13.9.2. В ранних версиях ассемблера ЭВМ PDP-11 вместо обозначений R0, R1, . . .
..., R5, SPh PC использовались %0, %1, . . . , %7. Напишите инструкции, включая
блок повторения, для определения соответствующих регистровых обозначений в терми-
нах %ft ... ,%7.
13.12.1. В окончательном варианте макроинструкции SAVE (см. рис. 13.12.1) ис-
пользуется список аргументов C,D,E,F,G,H. Почему мы не могли использовать А,В,
C,D,E,F?
13.12.2. Напишите макроинструкцию для восстановления (RESTORE) содержимого
до шести регистров путем выталкивания из стека.
13.12.3. Напишите макроинструкцию RESTORE для выталкивания содержимого до
шести регистров из стека, но напишите этот вариант так, чтобы он был парным к мак-
роинструкции SAVE в том смысле, что номера регистров могли бы передаваться обоим
макроинструкциям в одном и том же порядке. Например, SAVE 1, 4 должна проталки-
вать с (R1) и c(R4) для того, чтобы RESTORE 1, 4 могла вытолкнуть с (Rl) и c(R4),ho
в таком порядке: сначала с (R4), а затем с (R1).
13.12.4. Обращаясь к определению макроинструкции SHIFTLEFT на рис. 13.12.2,
определите, каким будет расширение при каждом из следующих вызовов:
a) SHIFTLEFT R4, 2
б) SHIFTLEFT (R4), 2
316
317
13.12.5. Каким будет действие SHIFTLEFT (R2)+, 5? (Определение SHIFTLEFT см.
на рис. 13.12.2.)
13.13.1. Используя блоки условий и подусловий, модифицируйте определение макро-
инструкции SHIFTLEFT так, чтобы она сдвигала байты так же, каки слова.
13.13.2. Наш окончательный вариант макроинструкции SHIFTLEFT (см. рис. 13.13.2)
все-таки обладает серьезным недостатком. Если мы пожелаем сдвинуть слово, адрес ко-
торого находится в R0, то будем вызывать макроинструкцию, например с помощью
SHIFTLEFT (R0), 5. Но тогда проверка в строке 7
.IF IDN TARGET, R0
даст ложное значение, поскольку (R0) не идентично R0. Опять расширение макроин-
струкции сопряжено с трудностями. Для исправления положения потребуется пере-
записать макроопределение, и мы даем некоторые предложения.
а) Не используйте регистр для счета сдвигов. Вместо этого поместите счетчик сдви-
гов в стек. Тогда сдвигающий цикл станет таким:
D: ASL TARGET
DEC (SP)
BNE D
б) Если счетчик сдвигов помещается в стек инструкцией
MOV В, —(SP)
а не
MOV #В, —(SP)
то SHIFTLEFT придется вызывать, например, как SHIFTLEFT R2,#4, а не как SHIFT-
LEFT R2, 4. Но эго открывает целый ряд новых возможностей, таких как SHIFTLEFT
ADDR, R2 (где c(R2) = счетчик сдвигов) или SHIFTLEFT ADDR, CNT (где c(CNT) =
= счетчик сдвигов).
в) Покажите, что даже при этих модификациях положение исправлено не полностью,
поскольку невозможно сдвигать слово, которое в настоящее время находится на вер-
шине стека.
13.13.3. Модифицируйте макроинструкцию из упражнения 13.13.2 так, чтобы она
еще ближе эмулировала инструкцию ASH: если счетчик сдвигов положительный, слово
должно сдвигаться влево', если счетчик сдвигов отрицательный, слово должно сдвигать-
ся вправо. (Вероятно, это не настолько просто, как может показаться поначалу.)
13.13.4. Мы видели, что макроопределения могут быть вложенными и что макроин-
струкции могут вызывать другие макроинструкции. Фактически макроинструкция мо-
жет даже вызывать сама себя при условии, что уделено внимание предотвращению бес-
конечной рекурсии.
а) Покажите, что следующая макроинструкция будет генерировать код для вычисле-
ния факториала N, при условии что значение R1 сначала устанавливается равным 1.
.MACRO FACT N
.IF LE N-l
.MEXIT
.ENDC
MUL #N,R1
FACT N-l
.ENDM
б) Какие существуют ограничения на N?
в) Почему эта макроинструкция не будет выполняться правильно, если мы заменим
R1 на R2?
г) Модифицируйте эту макроинструкцию так, чтобы использовался регистр R2 для
получения результата двойной точности (32-битового). Какие теперь ограничения нала-
гаются HaN?
д) Можно ли эту макроинструкцию переписать так (или предварить ее другой макро-
инструкцией) , чтобы она могла определять, имеет ли дело с нечетным или с четным но-
мером регистра?
е) В разд. 8.10 мы видели другой пример рекурсии, когда подпрограмма определяла
адрес первого вхождения в блок памяти байта, содержащего нечетное число. Почему
нельзя эту рекурсивную процедуру записать в виде рекурсивной макроинструкции?
13.13.5. В табл. 13.11.3 перечислены шесть условий и парных им дополнений. Пока-
жите, что дополняющие условия не являются необходимыми, онц реализованы только
для удобства программиста.
13.13^6. Теперь читатель достиг того состояния, когда он может понять определения
макроинструкций ввода-вывода, используемых в тексте. В частности, посмотрите при-
ложение Б, разд. Б.7 и постройте расширения для макровызовов SOUT.ASC #ТЕХТ,
#12 и SOUT.ASC #ТЕХТ.
13.13.7. Условные блоки могут вкладываться друг в друга. Каким будет действие
следующих инструкций и директив при различных комбинациях, когда символы SYM-
BOL и FLAG будут определены или не определены?
.IF DF SYMBOL
BGE NEXT
.IFF
.IF NDF FLAG
BLE NEXT
.ENDC
.IFTF
DEC R0
.ENDC
Каким может быть эффект от определения SYMBOL или ELAG или обоих после этих
условных блоков?
13.13.8. Мы видели, что макроинструкция может вызывать другую макроинструк-
цию или саму себя. Рассмотрим аналогично две макроинструкции, АМАС и В МАС, каж-
дая из которых вызывает другую. Каким будет расширение при вызове АМАС (в ячей-
ке START:)?
.MACRO АМАС X,Y,Z
ВМАС Y,Z
.ENDM
.MACRO BMAC S,T
.IF В S
.MEXIT
.IFF
MOVR’S,-(SP)
.IF NB T
MOV R’T-(SP)
.ENDC
AMAC S,T
.ENDC
.ENDM
START: AMAC 1,2,3
13.13.9. Обратитесь к упражнениям 7.7.11, 7.7.14 и 7.7.15 и напишите макроинструк-
ции для эмуляции соответственно инструкций XOR, MUL и DIV.
318
319
13.13.10. Хотя ЭВМ PDP-11 имеет инструкции для циклического сдвига 16-битовых
слов влево или вправо (см. описание ROLh RORb приложении Б), это строго одноби-
товые циклические сдвиги. Они не являются истинными циклическими сдвигами слова,
поскольку бит переноса действует как 17-й бит слова, и циклические сдвиги происходят
через бит переноса. Напишите такую макроинструкцию ROTATE, которая выполняет
действительно 16-битовый циклический сдвиги аргументами которой являются регистр,
подлежащий циклическому сдвигу, и счетчик сдвигов, причем положительный счетчик
задает циклический сдвиг влево, а отрицательный - вправо. (Задавая подлежащее цик-
лическому сдвигу слово в регистре, мы упрощаем условие,и читатель, в конце концов,
может это ограничение снять.)
ПРИЛОЖЕНИЕ А. НАБОР ИНСТРУКЦИЙ ЭВМ PDP-11
АЛ. СИМВОЛЫ И СОКРАЩЕНИЯ
Ниже перечислены символы и сокращения, используемые в данном приложении.
Символ Значение
с с(...) С d dst D foe n N PC PSW r reg R s src S V X X Z l(SP) (SP)t a’” V V код условия (двоичный) содержимое ... код условия (восьмеричный) или бит С в PSW приемник (двоичный) приемник (регистр или ячейка памяти) приемник (восьмеричный) ячейка число (двоичное) число (восьмер1Йное) или бит N в PSW программный счетчик слово состояния процессора регистр (двоичный) регистр регистр (восьмеричный) источник (двоичный) источник (регистр, ячейка памяти или число) источник (восьмеричный) бит V в PSW пословное смещение (двоичное) пословное смещение (восьмеричное) бит Z в PSW протолкнуть в аппаратный стек вытолкнуть из аппаратного стека придается значение ... логическое И логическое ИЛИ логическое ИСКЛЮЧАЮЩЕЕ ИЛИ логическое НЕ Слово состояния процессора (PSW) Приоритет ЦП Коды условий 1 . , 1 N 1 Z 1 V 1 С 15 76543210
320
А.2. НАБОР ИНСТРУКЦИЙ В АЛФАВИТНОМ ПОРЯДКЕ МНЕМОНИК
ADC ПРИБАВИТЬ ПЕРЕНОС К ПРИЕМНИКУ 0055DD
Двоичный код: 0 000101 101 ddd ddd
Операция: dst +- c(dsf) + с (С)
Формат ассемблера: ADC dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбра- сывается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Устанавливается, если c(dsf) было равно 077777, и с (С) было равно 1; иначе сбрасывается. С : Устанавливается, если с (dst) было равно 177777, и с (С) было равно 1; иначе сбрасывается.
Описание: Прибавляет к содержимому приемника содержимое бита С.
ADCB ПРИБАВИТЬ ПЕРЕНОС К ПРИЕМНИКУ-БАЙТУ 1055DD
Двоичный код: 1 000.101 101 ddd ddd
Операция: Формат ассемблера: dst <- с (dst) + с (С) ADCB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Устанавливается, если с (dst) было 177, и с (С) было 1; иначе сбрасывается. С: Устанавливается, если с (dst) было 377, и с (С) было 1; иначе сбрасывается.
Описание: Прибавляет к содержимому приемника (байта) содер- жимое бита С.
ADD ПРИБАВИТЬ ИСТОЧНИК К ПРИЕМНИКУ 06SSDD
Двоичный код: 0 110 ш sss ddd ddd
Операция: dst«- c(src) + c (dst)
Формат ассемблера: ADD src, dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Устанавливается, если происходит переполнение (оба операнда имели один и тот же знак, а резуль- тат — противоположный знак); иначе сбрасывается. С: Устанавливается, если произошел перенос из бита 15; иначе сбрасывается.
Описание: Прибавляет содержимое источника к содержимому при- емника и помещает результат в приемник. Первоначаль- ное содержимое приемника теряется; содержимое источ- ника остается без изменений.
1 1 Зак. 2212
321
ASH АРИФМЕТИЧЕСКИ СДВИНУТЬ РЕГИСТР 072RSS
Двоичный код: 0 111 010 rrr sss sss
Операция: reg «- с (reg) , сдвинутое на п бит влево (п >0) или впра- во (п < 0), где п - это значение со знаком шести млад- ших битов источникаsrc.
Формат ассемблера: ASH src, reg
„ Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Устанавливается, если в любой момент во время сдвига самый старший бит регистра изменил значь-
ние; иначе сбрасывается. С: Загружается значением последнего бита, сдвинуто- го из регистра.
Описание: Шесть младших битов слова источника берутся как чис- ло со знакомп, так что-321О «п<+3110. Если п > 0, то сдвиг производится влево на п бит. Са- мый старший бит (бит 15) сдвигается в бит С в PSW. Освободившемуся самому младшему биту (биту 0) при- сваивается значение 0: 15 0 С-*— * • - ] -* 0 Если п < 0, то сдвиг производится вправо на п бит. Зна- чение освободившегося самого старшего бита (бита 15) воспроизводится. Самый младший бит сдвигается в бит Св PSW: 15 0
Комментарий: На некоторых моделях ЭВМ PDP-11 инструкция ASH не реализована.
ASHC АРИФМЕТИЧЕСКИ СДВИНУТЬ СКОМБИНИРОВАННЫЕ РЕГИСТРЫ O73RSS
Двоичный код: 0 111 011 rrrsss sss
Операция: reg, reg V 1 *“ c(reg, reg\/ 1), сдвинутые на n бит влево (n >0) или вправо (п < 0), где п - значение со знаком шести младших битов источника src.
Формат ассемблера: ASHC src, reg
Коды условий: N : Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V : Устанавливается, если в любой момент во время сдвига самый старший бит 32-битовой регистровой пары изменил значение; иначе сбрасывается. С: Загружается значением последнего бита, сдвинуто- го из регистровой пары.
322
Описание:
Регистровая napareg и reg V 1 (например, R2 и R2 V I =
— R3), рассматриваемая как двойное 32-битовое слово,
сдвигается влево или вправо на п бит. Шесть младших
битов слова источника берутся как число со знаком п,
так что-321О <+3110.
Если п > 0, то сдвиг производится влево на п бит. Са-
мый старший бит (бит 15 регистра reg) сдвигается в бит
С в PSW. Освободившемуся самому младшему биту
(биту 0 регистра reg\J 1) присваивается значение 0:
с
Если п < 0, то сдвиг производится вправо нам бит. Зна-
чение освободившегося самого старшего бита (бита 15
регистра reg) воспроизводится. Самый младший бит
(бит 0 регистра reg \/ 1) сдвигается в бит Св PSW:
15 О 15 О
—► Reg -Reg V 1 —;•- С
____I
Если номер регистра нечетный (так что reg = reg\/ 1),
то сдвиг влево эквивалентен инструкции ASH src, reg.
Сдвиг вправо - это 16-битовый поворот вправо содер-
жимого регистра reg:
15___________________________0
г-*- ^ед --
Комментарий: На некоторых моделях ЭВМ PDP-11 инструкция ASHC
не реализована.
ASL АРИФМЕТИЧЕСКИ СДВИНУТЬ ПРИЕМНИК ВЛЕВО OO63DD
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
0 000110 011 ddd ddd
dst *- c(dst), сдвинутое на один бит влево.
ASL dst
N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Загружается результатом операции ИСКЛЮЧАЮ-
ЩЕЕ ИЛИ над N и С (определенными после сдвига).
С: Загружается значением бита, сдвинутого из самого
старшего бита приемника.
Каждый бит слова приемника сдвигается на одну пози-
цию влево. Самый старший бит (бит 15) сдвигается в
бит С в PSW. Самому младшему биту (биту 0) присваи-
вается значение 0:
15 о
11*
323
Комментарий:
Эта инструкция выполняет умножение на 2 содержимого
приемника с учетом знака. Если после сдвига устанавли-
вается бит V, то в приемнике произошло изменение знака.
ASLB АРИФМЕТИЧЕСКИ СДВИНУТЬ ПРИЕМНИК-БАЙТ ВЛЕВО 1063DD
Двоичный код: 1 000 110 011 ddd ddd
Операция: dst *- c(dsr), сдвинутое на один бит влево.
Формат ассемблера: ASLB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат — 0; иначе сбрасы-
вается.
V: Загружается результатом операции ИСКЛЮЧАЮ-
ЩЕЕ ИЛИ над N и С (определенными после сдвига).
С: Загружается значением бита, сдвинутого из самого
старшего бита приемника.
Описание: Каждый бит приемника-байта сдвигается на одну пози-
цию влево. Самый старший бит (бит 7 или 15) сдвигает-
ся в бит С в PSW. Освободившемуся самому младшему
биту (биту 0 или 8) присваивается значение 0:
15________________8
С-*- -*---- 0 или
ASR АРИФМЕТИЧЕСКИ СДВИНУТЬ ПРИЕМНИК ВПРАВО 0062DD
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
Комментарий:
0 000 ПО 010 ddd ddd
dst «- c(dst), сдвинутое на один бит вправо.
ASR dst
N: Устанавливаются, если результат < 0; иначе сбрасы-
ваются.
Z : Устанавливается, если результант = 0; иначе сбрасы-
вается.
V: Загружается результатом операции ИСКЛЮЧАЮ-
ЩЕЕ ИЛИ над N и С (определенными после сдвига).
С: Загружается значением бита, сдвинутого из самого
младшего бита приемника.
Каждый бит приемника сдвигается на одну позицию
вправо. Самый старший бит (бит 15) воспроизводится.
Самый младший бит (бит 0) сдвигается в бит С в PSW:
Эта инструкция выполняет деление на 2 содержимого
приемника с учетом знака.
ASRB АРИФМЕТИЧЕСКИ СДВИНУТЬ ПРИЕМНИК-БАЙТ ВПРАВО 1062DD
Двоичный код: 1 000 110 010 ddd ddd
Операция: dst«- c(dst), сдвинутые на один бит вправо.
Формат ассемблера: ASRB dst
324
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы- вается.
Z : Устанавливается, если результат = 0; иначе сбрасы- вается.
V: Загружается результатом операции ИСКЛЮЧАЮ- ЩЕЕ ИЛИ над N и С (определенными после сдвига).
С: Загружается значением бита, сдвинутого из самого младшего бита приемника.
Описание: Каждый бит приемник а-байта сдвигается на одну пози-
цию вправо. Самый старший бит (бит 7 или 15) воспро-
изводится. Самый младший бит (бит 0 или 8) сдвигает-
ся в бит С в PSW:
15 8
-*с
J
или
ВСС ВЕТВЛЕНИЕ, ЕСЛИ ПЕРЕНОС СБРОШЕН 1030ХХХ
Двоичный код: 1 000 011 Охх ххх ххх
Операция: PC *- с(РС) + 2 X (смещение), если С = 0
Формат ассемблера: ВСС 1ос
Коды условий: Не меняются.
Описание: Младший байт инструкции - это пословное смещение со знаком от текущего значения PC к целевому адресу 1ос. Если С = 0, то удвоенное смещение прибавляется к с (PC). Если С = 1, то инструкция игнорируется.
BCS ВЕТВЛЕНИЕ, ЕСЛИ ПЕРЕНОС УСТАНОВЛЕН 1034ХХХ
Двоичный код: 1 000 011 1хх ххх ххх
Операция: PC «- с (PC) + 2 X (смещение), если С — 1.
Формат ассемблера: BCS 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции — это по/словное смещение со знаком от текущего значения PC к целевому адресу 1ос. Если С = 1, то удвоенное смещение прибавляется к с (PC). Если С = 0, то инструкция игнорируется.
BEQ ВЕТВЛЕНИЕ, ЕСЛИ РАВНО НУЛЮ 0014ХХХ
Двоичный код: 0 000 001 1хх ххх ххх
Операция: PC «- с (PC) + 2 X (смещение), если Z = 1.
Формат ассемблера: BGE 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции — это пословное смещение со знаком от текущего значения PC к целевому адресу toe. Если Z = 1, то удвоенное смещение прибавляется к с (PC). Если Z = 0, то инструкция игнорируется.
BGE ВЕТВЛЕНИЕ, ЕСЛИ БОЛЬШЕ ИЛИ РАВНО НУЛЮ 0020ХХХ
Двоичный код: 0 000 010 Охх ххх ххх
Операция: PC *- с (PC) + 2 X (смещение), если N у V = 0.
Формат ассемблера: BGE 1ос
Коды условий: Не изменяются.
1 1B Зак. 2212
325
Описание: Младший байт инструкции - это пословное смещение со знаком от текущего значения PC к целевому адресу 1ос. Если N у V = 0, то удвоенное смещение прибавляет- ся к с (PC). Если N у V = 1, то инструкция игнорирует- ся. (Таким образом, эта инструкция, когда следует за сложением двух положительных чисел, вызывает вет- вление, даже если результат сложения отрицательный.)
BGT ВЕТВЛЕНИЕ, ЕСЛИ БОЛЬШЕ НУЛЯ 0030ХХХ
Двоичный код: 0 000 011 Охх ххх ххх
Операция: PC «- с(РС) + 2 X (смещение), если Z V (N V V) — 0.
Формат ассемблера: BGT 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции — это пословное смещение со знаком от текущего значения PC до целевого адреса 1ос. Если Z V (Ny V) = 0, то удвоенное смещение при- бавляется к с (PC). Если Z V (N у V) = 1, то инструк- ция игнорируется (эта инструкция ведет себя аналогич- но инструкции BGE за исключением того, что она не вы- зывает ветвления при нулевом результате.)
BHI ВЕТВЛЕНИЕ, ЕСЛИ ВЫШЕ1 1010ХХХ
Двоичный код: 1 000 001 Охх ххх ххх
Операция: PC «- с (PC) + 2 X (смещение), если С V Z = 0.
Формат ассемблера: BHI 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение со знаком от текущего значения PC к целевому адресу 1ос. Если С V Z = 0, то удвоенное смещение прибавляет- ся к с (PC). Если CV Z = 1, то инструкция игнорирует- ся. (Таким образом, инструкция BHI, следующая за ин- струкцией СМР, вызовет ветвление, если первое сравни- ваемое число имеет значение без учета знака, большее, чем второе сравниваемое число.)
BHIS ВЕТВЛЕНИЕ, ЕСЛИ ВЫШЕ ИЛИ ТО ЖЕ САМОЕ 1030ХХХ
Двоичный код: 1 000 011 Охх ххх ххх
Операция:7 Формат ассемблера: PC «- с (PC) + 2 X (смещение), если С — 0. BHIS 1ос
Коды условий: Не изменяются.
Описание: Идентична инструкции ВСС, включена в набор инструк- ций для удобства программиста. (Когда инструкция BHIS следует за инструкцией СМР, ветвление происхо- дит, если первое сравниваемое число имеет значение без учета знака, большее или равное значению второго срав- ниваемого числа.)
1 Инструкции ветвления, в названии которых присутствуют слова ’’больше”, ’’мень-
ше”, относятся к случаю чисел с учетом знака, а инструкции со словами ’’выше”, ’’ни-
же” - к случаю чисел без учета знака. - Прим, перев.
326
BIC СБРОСИТЬ БИТЫ ПРИЕМНИКА ПО ИСТОЧНИКУ 04SSDD
Двоичный код: 0 100 sss sss ddd ddd
Операция: Формат ассемблера: Коды условий: dst«- c(src)] /\ c(dst). BIC src, dst N: Устанавливается, если результат < 0; иначе сбрасы-
Описание: вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Сбрасывается. С: Не изменяется. В приемнике сбрасывается каждый бит, соответствую-
щий единичному биту источника. Содержимое источни-
ка не изменяется.
BICB СБРОСИТЬ БИТЫ ПРИЕМНИКА-БАЙТА ПО ИСТОЧНИКУ 14SSDD
Двоичный код: 1100 sss sss ddd ddd
Операция: Формат ассемблера: Коды условий: dst «- ['-c(src)] Д c(dst) BICB src, dst N : Устанавливается, если результат < 0; иначе сбрасы-
Описание: вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Сбрасывается. С: Не изменяется. В приемнике-байте сбрасывается каждый бит, соответ-
ствующий единичному биту в источнике. Содержимое
источника-байта не изменяется.
BIS УСТАНОВИТЬ БИТЫ ПРИЕМНИКА ПО ИСТОЧНИКУ 05SSDD
Двоичный код: 0 101 sss sss ddd ddd
Операция: dst *- c(src) V c(dst)
Формат ассемблера: BIS src, dst
Коды условий: N: Устанавливается, если результате 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Не изменяется.
Описание: Содержимое приемника заменяется результатом опера-
ций ИЛИ над содержимым источника и первоначальным
содержимым приемника. Таким образом, в приемнике
устанавливается каждый бит, соответствующий единич-
ному биту в источнике. Все остальные биты приемника
не изменяются. Содержимое источника не изменяется.
BISB УСТАНОВИТЬ БИТЫ ПРИЕМНИКА-БАЙТА ПО ИСТОЧНИКУ I5SSDD
Двоичный код: Операция: Формат ассемблера: Коды условий: 11в* 1 101 sss sss ddd ddd dst «- c (src) V c(dsr) BISB src, dst N: Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. „„„
V: Сбрасывается.
С: Не изменяется.
Описание: Содержимое приемник а-байта заменяется результатом
операции ИЛИ над содержимым источника и первона-
чальным содержимым приемника. Таким образом, в
приемнике-байте устанавливается каждый бит, соответ-
ствующий единичному биту в источнике. Все остальные
биты приемника не изменяются. Содержимое источни-
ка-байта не изменяется.
BIT ПРОВЕРИТЬ БИТЫ ИСТОЧНИКА И ПРИЕМНИКА 03SSDD
Двоичный код: 0 011 sss sss ddd ddd
Операция: c(src) Д c(dst).
Формат ассемблера: BIT src, dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Не изменяется.
Описание: Выполняет логическую операцию И над содержимым
слов источника и приемника и соответственно устанав-
ливает коды условий N и Z. Ни содержимое источника,
ни содержимое приемника не изменяются.
BITB ПРОВЕРИТЬ БИТЫ БАЙТОВ ИСТОЧНИКА И ПРИЕМНИКА 13SSDD
Двоичный код: 1 011 sss sss ddd ddd
Операция: c (src) /\ c(dst).
Формат ассемблера: BITB src, dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Не изменяется.
Описание: Выполняет логическую операцию И над содержимым
байтов источника и приемника и соответственно уста-
навливает коды условий N и Z. Ни содержимое источни-
ка, ни содержимое приемника не изменяются.
BLE ВЕТВЛЕНИЕ, ЕСЛИ МЕНЬШЕ ИЛИ РАВНО НУЛЮ 0034ХХХ
Двоичный код: 0 000 011 1хх ххх ххх
Операция: PC «- с(РС) + 2 X (смещение), если Z V (N у V) = 1.
Формат ассемблера: BLE 1ос.
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если Z у (N у V) = 1, то удвоенное смещение при-
бавляется к с (PC). Если Z V (N у V) = 0, то инструк-
ция игнорируется (поведение этой инструкции близко
к поведению инструкции BLT, за исключением того,что
BLE вызываетветвление при нулевом результате).
328
BLO ВЕТВЛЕНИЕ, ЕСЛИ НИЖЕ 1034ХХХ
Двоичный код: 1 000 011 Ixxxxxxxx
Операция: PC«- с (PC) + 2 X (смещение), если С = 1.
Формат ассемблера: BLO 1ос.
Коды условий: Не изменяются.
Описание: Идентична инструкции BCS; включена в набор инструк-
ций для удобства программиста. (Когда инструкция
BLO следует за инструкцией СМР, ветвление происходит,
если первое сравниваемое число имеет значение без уче-
та знака, меныпее, чем значение второго сравниваемого
числа.)
BLOS ВЕТВЛЕНИЕ, ЕСЛИ НИЖЕ ИЛИ ТО ЖЕ САМОЕ 1014ХХХ
Двоичный код: 1 000 001 1х ххх ххх
Операция: PC <- с(РС) + 2 X (смещение), если С V Z = 1.
Формат ассемблера: BLOS foe
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если С V Z = 1, то удвоенное смещение прибавляет-
ся к с (PC). Если С V Z = 0, то инструкция игнорирует-
ся. (Когда инструкция BLOS следует за инструкцией
СМР, то ветвление происходит, если первое сравнивае-
мое число имеет значение без учета знака, меньшее или
равное значению второго сравниваемого числа.)
BLT ВЕТВЛЕНИЕ, ЕСЛИ МЕНЬШЕ НУЛЯ 0024ХХХ
Двоичный код: 0 000 010 1хх ххх ххх
Операция: PC <- с (PC) + 2 X (смещение), если N V V = 1.
Формат ассемблера: BLT foe
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если N у V = 1, то удвоенное смещение прибавляет-
ся к с (PC). Если N V V = 0, то инструкция игнорирует-
ся. (Когда инструкция BLT следует за инструкцией
СМР, то она вызывает ветвление, если первое сравнива-
емое число отрицательное, а второе положительное, да-
же если при этом происходит переполнение. Если первое
сравниваемое число положительное, авторов отрицатель-
ное, то эта инструкция ветвления не вызывает.)
BMI ВЕТВЛЕНИЕ, ЕСЛИ МИНУС 1004ХХХ
Двоичный код: 1 000 000 1хх ххх ххх
Операция: PC «- с(РС) + 2 X (смещение), если N = 1.
Формат ассемблера: BMI 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если N — 1, то удвоенное смещение прибавляется к
с (PC). Если N = 0, то инструкция игнорируется.
329
BNE ВЕТВЛЕНИЕ, ЕСЛИ HE НОЛЬ 0010ХХХ
Двоичный код: О ООО 001 Охх ххх ххх
Операция: PC *- с (PC) + 2 X (смещение), если Z = 0.
Формат ассемблера: BNE 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции — это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если Z = 0, то удвоенное смещение прибавляется к
с (PC). Если Z = 1, то инструкция игнорируется.
BPL ВЕТВЛЕНИЕ, ЕСЛИ ПЛЮС 1000ХХХ
Двоичный код: 1 000 000 Охх ххх ххх
Операция: PC *- с(РС) + 2 X (смещение) ; если N = 0.
Формат ассемблера: BPL 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если N — 0, то удвоенное смещение прибавляется к
с (PC). Если N = 1, то инструкция игнорируется.
ВРТ ПЕРЕЙТИ К ЛОВУШКЕ ТОЧКИ ПРЕРЫВАНИЯ 000003
Двоичный код: 0 000 000 000 000 011
Операция: l(SP) c(PSW)
4(SP)«- с (PC)
РС-с(14)
PSW*-c(16)
Формат ассемблера: ВРТ
Коды условий: Загружаются из второго слова вектора ловушки.
Описание: Выполняет переход к ловушке по (фиксированным)
ячейкам памяти 000014 - 000016.
BR ВЕТВЛЕНИЕ (БЕЗУСЛОВНОЕ) 0004ХХХ
Двоичный код: 0 000 000 1хх ххх ххх
Операция: PC «- с (PC) + 2 X (смещение).
Формат ассемблера: BR/oc
Коды условий: Не изменяются.
Описание: Младший байт инструкции - это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Удвоенное смещение прибавляется к с (PC) (без ка-
ких-либо условий).
BVC ВЕТВЛЕНИЕ, ЕСЛИ ИНДИКАТОР ПЕРЕПОЛНЕНИЯ СБРОШЕН 1020ХХХ
Двоичный код: 1 000 010 Охх ххх ххх
Операция: PC *- с (PC) + 2 X (смещение), если V = 0.
Формат ассемблера: BVC 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции — это пословное смещение
со знаком от текущего значения PC к целевому адресу
1ос. Если V = 0, то удвоенное смещение прибавляется к
с (PC). Если V = 1, то инструкция игнорируется.
330
BVS ВЕТВЛЕНИЕ, ЕСЛИ ИНДИКАТОР ПЕРЕПОЛНЕНИЯ УСТАНОВЛЕН 1024ХХХ
Двоичный код: 1 000 010 1 хх ххх ххх
Операция: PC — с(РС) + 2 X (смещение), если V 1.
Формат ассемблера: BVS 1ос
Коды условий: Не изменяются.
Описание: Младший байт инструкции — это пословное смещение со знаком от текущего значения PC к целевому адресу 1ос. Если V = 1, то удвоенное смещение прибавляется к с (PC) . Если V = 0, то инструкция игнорируется.
ССС СЁРОСИТЬ ВСЕ КОДЫ УСЛОВИЙ 000257
Двоичный код: 0 000 000 010 101 111
Операция: С — 0, V — 0, Z — 0, N — 0.
Формат ассемблера: ССС
Коды условий: Все сбрасываются.
Описание: Сбрасывает все коды условий.
Комментарий: Смотри, также инструкцию CLc.
CLc СБРОСИТЬ ЗАДАННЫЙ КОД УСЛОВИЯ 00024СС
Двоичный код: 0 000 000 010 10с ссс
Операция: Заданный код (коды) условий — 0
Формат ассемблера: CLc
Коды условий: Заданный код (коды) сбрасывается. Все остальные ко- ды условий не изменяются.
Описание: Биты 3, 2, 1 и 0 инструкции соответствуют кодам усло- вий N, Z, V и С соответственно. Если какие-либо из этих битов в инструкции установлены, то соответствующие коды условий будут сброшены.
Мнемоника Код Действие
NOP 000240 (Никакие коды условий не сбрасываются)
CLC 000241 С-0
CLV 000242 V-0
Нет 000243 С-0, V-0
CLZ 000244 Z -0
Нет 000245 C-0.Z -0
Нет 000246 V-0, Z-0
Нет 000247 С-0, V-0.Z-0
CLN 000250 N -0
Нет 000251 С - 0, N - 0
Нет 000252 V - 0, N- 0
Нет 000253 С-0, V-0, N-0
Нет 000254 Z -0, N-0
Нет 000255 C-O.Z-O, N - 0
Нет 000256 V-0, Z -0, N-0
ССС 000257 С-0, V - 0.Z -0, N - 0
Комментарий:
Те инструкции, для которых отсутствуют мнемоники
ассемблера ЭВМ PDP-11, могут быть получены с помо-
щью директивы .WORD, содержащей соответствующий
код.
331
CLR
СБРОСИТЬ ПРИЕМНИК
0050DD
Двоичный код: О ООО 101 000 ddd ddd
Операция: dst -- О
Формат ассемблера: CLR dst
Коды условий: N: Сбрасывается.
Z: Устанавливается.
V: Сбрасывается.
С: Сбрасывается.
Описание: Слову приемника присваивается значение 0.
CLRB СБРОСИТЬ ПРИЕМНИК-БАЙТ 1050DD
Двоичный код: 1 000 101 000 ddd ddd
Операция: dst <- 0.
Формат ассемблера: CLRB dst
Коды условий: N: Сбрасывается.
Z: Установлен.
V: Сбрасывается.
С: Сбрасывается.
Описание: Приемнику-байту присваивается значение 0.
СМР СРАВНИТЬ ИСТОЧНИК С ПРИЕМНИКОМ 02SSDD
Двоичный код: 0 010 sss sss ddd ddd
Операция: c(src) + [~ c(dst) + 1].
Формат ассемблера: CMP src, dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Устанавливается, если было переполнение (операн-
ды имели противоположные знаки, а знак результа-
та такой же, как знак приемника); иначе сбрасы-
вается.
С: Устанавливается, если не было переноса из самого
старшего бита результата; иначе сбрасывается.
Описание: Вычитает содержимое приемника из содержимого источ-
ника и устанавливает коды условий в соответствии с
результатом. Ни содержимое источника, ни содержимое
приемника не изменяются. (Обычно эта инструкция со-
провождается какой-либо инструкцией условного вет-
вления.)
СМРВ СРАВНИТЬ ИСТОЧНИК-БАЙТ С ПРИЕМНИКОМ-БАЙТОМ 12SSDD
Двоичный код: 1 010 sss sss ddd ddd
Операция: c(src)+ [~c(c?s/) + 1 ].
Формат ассемблера: СМРВ src, dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0.
V: Устанавливается, если было переполнение (операн-
ды имели противоположные знаки, а знак результа-
та такой же, как знак приемника); иначе сбрасы-
вается.
С: Устанавливается, если не было переноса из самого
332 старшего бита результата; иначе сбрасывается.
Описание: Вычитает содержимое приемника-байта,из содержимого
источника-байта и устанавливает коды условий в соот-
ветствии с результатом. Ни содержимое источника бай-
та, ни содержимое приемника-байта не изменяется.
(Обычно эта инструкция сопровождается какой-либо
инструкцией условного ветвления.)
СОМ ВЗЯТЬ ДОПОЛНЕНИЕ ПРИЕМНИКА 0051DD
Двоичный код: 0 000 101 001 ddd ddd
Операция: dst *—c(dst).
Формат ассемблера: COM dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Устанавливается.
Описание: Байт приемника заменяется его дополнением до едини-
цы (каждый нулевой бит устанавливается в 1, а каждый
единичный бит сбрасывается в 0).
COMB ВЗЯТЬ ДОПОЛНЕНИЕ ПРИЕМНИКА-БАЙТА 1051DD
Двоичный код: 1 000 101 001 ddd ddd
Операция: Формат ассемблера: dst c(dst). COMB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбра- сывается. Z: Устанавливается, если результат = 0; иначе сбра- сывается. V: Сбрасывается. С: Устанавливается.
Описание: Байт приемника заменяется его дополнением до еди- ницы (каждый нулевой бит устанавливается в 1, а каж- дый единичный бит сбрасывается в 0).
DEC УМЕНЬШИТЬ ПРИЕМНИК 0053DD
Двоичный код: 0 000 101 011 ddd ddd
Операция: dst c(dst) - }.
Формат ассемблера: DEC dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
Z : вается. Устанавливается, если результат = 0; иначе сбрасы-
V: вается. Устанавливается, если с (dst) было равно 1000000;
С: иначе сбрасывается. Не изменяется.
Описание: Вычитает 1 из содержимого слова приемника.
DECB УМЕНЬШИТЬ ПРИЕМНИК-БАЙТ 1053DD
Двоичный код: Операция: Формат ассемблера: Коды условий: 1 000 101 011 ddd ddd dst <- c(dst) - 1. DECB dst N: Устанавливается, если результат < 0; иначе сбрасы- вается.
333
Z : Устанавливается, если результат — 0; иначе сбрасы- вается. V: Устанавливается, если с(dst) было равно 200; иначе сбрасывается. С: Не изменяется.
Описание: Вычитает 1 из содержимого приемника-байта.
DIV РАЗДЕЛИТЬ РЕГИСТР НА ИСТОЧНИК 071RSS
Двоичный код: 0 111 001 rrr sss sss
Операция: reg, reg V 1 c(reg, reg\j 1)4- c(src).
Формат ассемблера: DIV src, reg
Коды условий: N: Устанавливается, если частное < 0; иначе сбрасыва- ется. Z: Устанавливается, если частное = 0; иначе сбрасы- вается. V: У станавливается, если номер регистра нечетный, или если c(src) = 0, или если абсолютное значение с (reg) больше абсолютного значения с (src) ; иначе сбрасы- вается. С: Устанавливается, если с(src) = 0; иначе сбрасывает- /*<г
Описание: им* 32-битовое содержимое reg, reg V 1 делится на содержи- мое источника. Частное остается в reg, а остаток — в reg V 1. Остаток имеет тот же знак, что и делимое. Если номер регистра нечетный, то деление не выполняется, а устанавливается бит V.
Комментарий: На некоторых моделях ЭВМ PDP-11 эта инструкция не реализована.
ЕМТ ПЕРЕЙТИ К ЛОВУШКЕ ЭМУЛЯТОРА 104000- 104377
Двоичный код: 1 000 100 Опп ппп ппп
Операция: !(SP) *- c(PSW) l(SP) - с(РС) РС«- с(30) PSW-c(32)
Формат ассемблера: ЕМТ MW
Коды условий: Загружаются из второго слова вектора ловушки.
Описание: Выполняет переход к ловушке через вектор в (фикси- рованных) ячейках памяти 000030 - 000032. Младший байт инструкции декодером ЦП не использует- ся. Однако ассемблер ЭВМ PDP-11 поместит в младшем байте ассемблированной инструкции любое число NNN, появляющееся как операнд (0 <NNN < 377), после че- го этот байт может быть доступен программе пользова- теля.
Комментарий: Во многих операционных системах эта инструкция ис- пользуется для обращения к утилитам, поэтому пользо- вателю рекомендуется использовать вместо нее инструк- цию TRAP.
HALT ОСТАНОВИТЬ ПРОЦЕССОР 000000
Двоичный код: 0 000 000 000 000 000
Операция: Останов процессора.
Формат ассемблера: HALT
334
Коды условий: Не изменяются.
Описание: Прекращает работу процессора.
INC УВЕЛИЧИТЬ ПРИЕМНИК 0052DD
Двоичный код: О ООО 101 010 ddd ddd
Операция: dst«- с (dst) + 1.
Формат ассемблера: INC dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Устанавливается, если c(dst) было равно 077777,
иначе сбрасывается.
С: Не изменяется.
Описание: Прибавляет 1 к содержимому слова приемника.
INCB УВЕЛИЧИТЬ ПРИЕМНИК-БАЙТ 1052DD
Двоичный код: 1 000 101 010 ddd ddd
Операция: dst >- c(dst) + 1.
Формат ассемблера: INCB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат — 0; иначе сбрасы-
вается.
V: Устанавливается, если c(dst) было равно 177,иначе
сбрасывается.
С: Не изменяется.
Описание: Прибавляет 1 к содержимому приемника-байта.
ЮТ ПЕРЕЙТИ К ЛОВУШКЕ ВВОДА-ВЫВОДА 000004
Двоичный код: 0 000 000 000 000100
Операция: ! (SP) «- с( PSW)
4(SP) *-с(РС)
PC *- с(20)
PSW-c(22)
Формат ассемблера: ЮТ
Коды условий: Загружаются из второго слова вектора ловушки.
Описание: Выполняет переход к ловушке через вектор в (фикси-
рованных) ячейках памяти 000020 - 000022.
JMP ПЕРЕЙТИ К ПРИЕМНИКУ 0001DD
Двоичный код: 0 000 000 001 ddd ddd
Операция: PC «- адрес dst
Формат ассемблера: JMP dst
Коды условий: Не изменяются.
Описание: Программному счетчику PC присваивается значение ад-
реса приемника.
JSR ПЕРЕЙТИ К ПОДПРОГРАММЕ 004RDD
Двоичный код: 0 000 100 rrr ddd ddd
Операция: temp <- адресdst
|(SP) «- c(reg)
reg «- c(PC)
PC «- c{temp)
(где temp - это внутренний регистр процессора).
335
Формат ассемблера: JSRreg, dst
Коды условий: Не изменяются.
Описание: Эта инструкция вычисляет адрес приемника, сохраняет содержимое заданного регистра в аппаратном стеке (ре- гистр для перехода к подпрограмме) и сохраняете этом регистре текущее значение PC. (Такое сохранение теку- щего значения PC обеспечивает связь из подпрограммы назад в вызывающую программу.) Наконец, PC присва- ивается значение адреса приемника, что фактически по- рождает переход к подпрограмме. Возврат в вызывающую подпрограмму обеспечивается парной инструкцией RTS.
MARK ПОМЕТИТЬ СТЕК ДЛЯ ОЧИСТКИ 0064NN
Двоичный код: 0 000 110 100 ппп ппп
Операция: SP *- с (PC) + 2 X (NN) PC - c(R5) R5-(SP)t
Формат ассемблера: MARK NN
Коды условий: He изменяются.
Описание: Когда аргументы передаются подпрограмме путем по- мещения их в аппаратный стек, эта инструкция может использоваться для очистки стека при возврате из под- программы, как показано в следующем примере: MOV R5, -(SP) MOV APG1,-(SP)
MOV ARGK, (SP)
MOV «MARK K>, -(SP)
MOV SP, R5
JSR PC, SUBR
(где «MARK K> обозначает код инструкции MARK К).
В момент, когда управление передается подпрограмме
SUBR, содержимое стека будет таким, как показано
на рисунке, а в результате выполнения инструкции
MOV SP,R5 содержимое регистра R5 будет таким:
c(R5) = SB - 2 X К - 4. (Здесь ML обозначает стержне-
вую ветвь программы, a SB - первоначальный базовый
Если теперь выполняется возврат из подпрограммы с
помощью инструкции RTS R5 (но не с помощью RTS ’
PC), то произойдет следующее:
336
PC «- c(R5) = SB - 2 X К - 4
R5 - (SP)t = c(ML PC)
В этом месте c (PC) = SB — 2 X К — 4, так что извлекает-
ся инструкция MARK К, что увеличивает с (PC) до SB
- 2К - 2. Действие инструкции MARK К таково:
SP- с(РС)+ 2 X К =
-SB-2XK-2+2XK — SB — 2
РС^ c(R5) = c(MLPC)
R5-(SP)t
Это последнее выталкивание из аппаратного стека вос-
станавливает в R5 его первоначальное значение (из
стержневой ветви) и возвращает указатель стека к его
первоначальному значению SB.
Комментарий: На некоторых моделях ЭВМ PDP-11 эта инструкция не
реализована.
MOV ПЕРЕСЛАТЬ ИСТОЧНИК В ПРИЕМНИК 01SSDD
Двоичный код: 0 001 sss sss ddd ddd
Операция: dst +- c(src)
Формат ассемблера: MOV src, dst
Коды условий: N: Устанавливается, если c(src) < 0; иначе сбрасыва-
ется.
Z: Устанавливается, если с(src) = 0; иначе сбрасыва-
ется.
V: Сбрасывается.
С: Не изменяется.
Описание: Пересылает копию содержимого слова источника в при-
емник. Первоначальное содержимое приемника теряет-
ся; содержимое слова источника не изменяется.
MOVB ПЕРЕСЛАТЬ ИСТОЧНИК-БАЙТ В ПРИЕМНИК 11SSDD
Двоичный код: 1 001 sss sss ddd ddd
Операция: dst <- c(src)
Формат ассемблера: MOVB src, dst
Коды условий: N: Устанавливается, если c(src) < 0; иначе сбрасыва-
ется.
Z: Устанавливается, если с(src) -- 0; иначе сбрасыва-
ется.
V: Сбрасывается.
С: Не изменяется.
Описание: Пересылает копию содержимого источника-байта в при-
емник-байт. Первоначальное содержимое приемника те-
ряется; содержимое источника-байта не изменяется.
Замечание. Если приемником является регистр, то пере-
сылка происходит в младший байт регистра-приемника.
Старший байт регистра-приемника заполняется значени-
ем самого старшего бита источникагбайта (что создает
эффект сохранения арифметического знака содержимо-
го источника-байта).
MUL УМНОЖИТЬ РЕГИСТР НА ИСТОЧНИК 070RSS
Двоичный код: 0 111 000 rrr sss sss
Операция: reg, reg \J 1 *- c(reg) X c(src).
Формат ассемблера: MUL src, reg
337
Коды условий: N: Устанавливается, если произведение < 0; иначе
сбрасывается. Z: Устанавливается, если произведение = 0; иначе сбрасывается. V: Сбрасывается. С: Устанавливается, если произведение меньше — 21S или больше 2IS — 1; иначе сбрасывается.
Описание: Содержимое 16-битового регистра умножается на содер- жимое 16-битового источника, что дает 32-битовое про- изведение в reg, reg V1. Если номер регистра нечетный (так что reg = reg V1), то результатом будет 16-битовое произведение в reg, а старшие 16 бит результата будут потеряны. (Если произведение достаточно велико, что- бы в случае нечетного регистра терялись биты, то будет
устанавливаться бит С в PSW).
Комментарий: На некоторых моделях ЭВМ PDP-11 эта инструкция не реализована.
NEG ИЗМЕНИТЬ ЗНАК ПРИЕМНИКА 0054DD
Двоичный код: 0 000 101 100 ddd ddd
• Операция: Формат ассемблера: Коды условий: Описание: dst i~ c(dsf) + 1. NEG dst N: Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Устанавливается, если результат = 100000; иначе сбрасывается. С: Сбрасывается, если результат = 0; иначе устанавли- вается. Слово приемника заменяется его дополнением до двух (отрицательным значением).
NEGB ИЗМЕНИТЬ ЗНАК ПРИЕМНИКА-БАЙТА 1054DD Двоичный код: 1 000 101 100 ddd ddd Операция: dst «— c(dst) + 1. Формат ассемблера: NEGB dst Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы- вается. • Z: Устанавливается, если результат = 0; иначе сбрасы- вается. V: Устанавливается, если результат = 200; иначе сбра- сывается. С: Сбрасывается, если результат = 0; иначе устанав- ливается. Описание: Байт приемника заменяется его дополнением до двух (отрицательным значением).
NOP НЕТ ОПЕРАЦИИ Двоичный код: Операция: Формат ассемблера: Коды условий: 000240 0 000 000 010 100 000 Отсутствует. NOP Не изменяются.
338
Описание: Эта инструкция такая же, как "не сбрасывать никаких
кодов условий” (см. CLc). Хотя зга инструкция ника-
кого действия не производит, она занимает одно слово
памяти и требует полного цикла при ’’выполнении”. (Эта
инструкция дается для удобства программиста. Напри-
мер, если разрабатываемая программа содержит ’’точ-
ки прерывания”, то инструкция ВРТ может временно
заменяться путем перекрытия их инструкцией NOP.)
RESET СБРОСИТЬ ШИНУ В ИСХОДНОЕ СОСТОЯНИЕ 000005
Двоичный код: 0 000 000 000 000 101
Операция: Посылает по шине сигнал INIT.
Формат ассемблера: RESET
Описание: Сбрасывает все устройства на шине в состояние, которое
они имеют после подачи питания.
ROL ЦИКЛИЧЕСКИ СДВИНУТЬ ВЛЕВО ПРИЕМНИК 0061DD
Двоичный код: 0 000 110 001 ddd ddd
Операция: С, dst <- с(С, dst), циклически сдвинутое на один бит
влево.
Формат ассемблера: ROL dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Загружается результатом операции ИСКЛЮЧАЮ-
ЩЕЕ ИЛИ над N и С (какими они оказываются
после циклического сдвига).
С: Загружается значением бита, сдвинутым из самого
старшего бита приемника.
Описание: Бит С в PSW и слово приемника, рассматриваемые как
17-битовое ’’слово”, циклически сдвигаются влево на
один бит. В освободившийся самый младший бит при-
емника (бит 0) загружается содержимое бита С. Значе-
ние самого старшего бита приемника (бита 15) загружа-
ется в бит С:
15 0
С **------
ROLB ЦИКЛИЧЕСКИ СДВИНУТЬ ВЛЕВО ПРИЕМНИК-БАЙТ 1061DD
Двоичный код: 1 000 110 001 ddd ddd
Операция: С, dst *- с(С, dst), циклически сдвинутое на один бит
влево.
Формат ассемблера: ROLB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Загружается результатом операции ИСКЛЮЧАЮ-
ЩЕЕ ИЛИ над N и С (какими они оказываются
после циклического сдвига).
339
Описание:
С: Загружается значением бита, сдвинутым из самого
старшего бита приемника.
Бит С в PSW и байт приемника, рассматриваемые как де-
вятибитовый ’’байт”, циклически сдвигаются влево на
один бит. В освободившийся самый младший бит прием-
ника (бит 0 или 8) загружается содержимое бита С. Зна-
чение самого старшего бита приемника (бита 7 или 15)
загружается в бит С:
или
ROR ЦИКЛИЧЕСКИ СДВИНУТЬ ВПРАВО ПРИЕМНИК 0060DD
Двоичный код: О ООО 110 000 ddd ddd
Операция: С, dst *- с(С, dst), циклически сдвинутое вправо на один
* бит.
Формат ассемблера: ROR dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Загружается результатом операции ИСКЛЮЧАЮ-
ЩЕЕ ИЛИ над N и С (какими оказываются после
циклического сдвига).
С: Загружается значением бита, сдвинутого из самого
младшего бита приемника.
Описание: Бит С в PSW и слово приемника, рассматриваемые как
17-битовое ’’слово”, циклически сдвигаются вправо на
один бит. В освободившийся самый старший бит (бит
15) загружается содержимое бита С. Значение самого
младшего бита приемника (бита 0) загружается в бит С:
RORB ЦИКЛИЧЕСКИ СДВИНУТЬ ВПРАВО ПРИЕМНИК-БАЙТ 1060DD
Двоичный код: 1000110 000 ddd ddd
Операция: С, dst *- с(С, dst), циклически сдвинутое вправо на один бит.
Формат ассемблера: RORB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы- вается. Z : Устанавливается, если результат = 0; иначе сбрасы- вается. V: Загружается результатом операции ИСКЛЮЧАЮ- ЩЕЕ ИЛИ над N и С (какими они оказываются после сдвига). С: Загружается значением бита, сдвинутого из самого младшего бита приемника.
340
RTI
RTS
Описание:
Бит С в PSW и байт приемника, рассматриваемые как
девятибитовый ’’байт”, циклически сдвигаются на один
бит вправо. В освободившийся самый старший бит (бит
7 или 15) загружается содержимое бита С. Значение са-
мого младшего бита байта приемника (бит 0 или 8) за-
гружается в бит С:
15________________8
С »
или
ВЕРНУТЬСЯ ИЗ ПРЕРЫВАНИЯ
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
000002
О ООО 000 000 000 010
PC <- (SP) t
PSW«-(SP)t
RTI
Загружаются из аппаратного стека.
Содержимое PC в PSW восстанавливается из аппаратно-
го стека. (Эта инструкция используется для возврата
из подпрограмм обработки прерываний или обработки
ловушки.)
ВЕРНУТЬСЯ ИЗ ПОДПРОГРАММЫ
00020R
Двоичный код: 0 000 000 010 000 rrr
Операция: PC с (г eg )
reg*-(SP)t
Формат ассемблера: RTS reg
Коды условий: Не изменяются.
Описание: Содержимое заданного регистра загружается в PC, а в
регистр выталкивается верхний элемент аппаратного сте-
ка. Эта инструкция используется для возврата из под-
программ.
RTT
ВЕРНУТЬСЯ ИЗ ЛОВУШКИ
000006
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
Комментарий:
0 000 000 000 000 ПО
PC «- (SP)t
PSW*-(SP)t
RTT
Загружаются из аппаратного стека.
Содержимое PC и PSW восстанавливается из аппаратно-
го стека. (Эта инструкция используется для возврата
из подпрограмм обработки прерываний или обработки
ловушек.) Инструкция RTT идентична инструкции RTI
за исключением поведения, зависящего от состояния
бита 4 в PSW (см. с. 218).
На некоторых моделях ЭВМ PDP-11 эта инструкция не
реализована.
SBC
ВЫЧЕСТЬ ПЕРЕНОС ИЗ ПРИЕМНИКА
0056DD
Двоичный код: 0 000101 ПО ddd ddd
Операция: dst «- c(dst) - с(С).
Формат ассемблера: SBC dst
341
Коды условий:
Описание:
N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z: Устанавливается, если результат — 0; иначе сбрасы-
вается.
V: Устанавливается, если с (dst) было равно 100000;
иначе сбрасывается.
С: Сбрасывается, если с(dst) было равно 000000 и
с (С) было равно 1; иначе устанавливается.
Вычитает содержимое бита С из содержимого приемника.
SBCB
ВЫЧЕСТЬ ПЕРЕНОС ИЗ ПРИЕМНИКА-БАЙТА
1056DD
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
1 000101 110 ddd ddd
dst +- c(dst) - c(C)
SBCB dst
N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Устанавливается, если с(dst) было равно 200; иначе
сбрасывается.
С: Сбрасывается, если с (dst) было равно 000 и с (С)
было равно 1; иначе устанавливается.
Вычитает содержимое бита С из содержимого байта при-
емника.
see
УСТАНОВИТЬ ВСЕ КОДЫ УСЛОВИЙ
000277
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
Комментарий:
0 000 000 010 111 111
С<-1, V<-1,Z^ 1,N«-1.
SCC
Все устанавливаются.
Устанавливает все коды условий.
Смотрите также инструкцию SEc.
SEc
УСТАНОВИТЬ ЗАДАННЫЙ КОД УСЛОВИЯ 00026СС
Двоичный код: 0 000 000 010 11с есс
Операция: Заданный код (коды) условия <-1.
Формат ассемблера: SEc
Коды условий: Заданный код (коды) условий устанавливается.
Все остальные коды не изменяются.
Описание: Биты 3, 2, 1 и 0 инструкции соответствуют кодам усло-
вий N, Z, V и С соответственно. Если какие-то из этих
битов в инструкции установлены, то будут устанавли-
ваться соответствующие коды условий.
Мнемоника Код Действие
NOP 000260 (Никакие коды условий не устанавливаются.)
SEC 000261 С*-1
SEV 000262 V*-l
Нет 000263 С*-1, V*- 1
, SEZ 000264 Z^ 1
Нет 000265 C^l.Z-l
SOB
Окончание
Мнемоника Код Действие
Нет 000266 V - 1, Z <- 1
Нет 000267 С- 1, V<- 1,Z «- 1
SEN 000270 N *- 1
Нет 000271 С- 1, N ♦- 1
Нет 000272 V 1, N *- 1
Нет 000273 С- 1, V*- 1, N <-1
Нет 000274 Z 1, N-1
Нет 000275 С< 1, Z ♦-1, N ♦-1
Нет 000276 V *- 1, Z «- 1, N <- 1
SCC 000277 С- 1,V«-1,Z«-1,N<-1
Ко ммен тарий:
Те инструкции, для которых не существует мнемоник
ассемблера ЭВМ PDP-11, могут быть получены с помо-
щью директивы .WORD, содержащей соответствующий
код.
ВЫЧЕСТЬ ЕДИНИЦУ И ВЕРНУТЬСЯ НАЗАД
077RXX
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
Комментарий:
0111 111 ггг ххх ххх
reg <- c(reg) - 1;
PC <- с(РС) — 2 X (смещение), если с (reg) ¥= 0.
SOB reg, loc
He изменяются.
Шесть младших битов инструкции - это пословное сме-
щение без знака -от текущего PC к целевому адресу loc.
Содержимое заданного регистра уменьшается на 1. Если
результат ненулевой, то двойное смещение вычитается
из с (PC). Если после уменьшения содержимое регистра
равно 0, то ветвления не происходит, а извлекается сле-
дующая инструкция.
Эта инструкция используется для управления циклами,
когда reg действует как счетчик цикла.
Передача управления может происходить только в об-
ратном направлении (к нижним адресам памяти).
На некоторых моделях ЭВМ PDP-11 эта инструкция не
реализована.
SPL
УСТАНОВИТЬ УРОВЕНЬ ПРИОРИТЕТА
00023N
Двоичный код:
Операция:
Формат ассемблера:
Коды условий:
Описание:
Комментарий:
0 000 000 010 011 ппп
Биты приоритета в PSW *- N.
SPL.V
Не изменяются.
Три младших бита инструкции загружаются в биты при-
оритета в PSW (в биты 7, 6 и 5). Эта инструкция уста-
навливает уровень приоритет ЦП равным N.
На некоторых моделях ЭВМ PDP-11 эта инструкция не
реализована.
343
342
SUB
ВЫЧЕСТЬ ИСТОЧНИК ИЗ ПРИЕМНИКА 16SSDD
Двоичный код: 1 110 sss sss ddd ddd
Операция: dst *- c(dst) + [~ c(src) + 1].
Формат ассемблера: SUB src, dst
Коды условий: N : Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Устанавливается, если было переполнение (операн-
ды имели противоположные знаки, а знак источни-
ка был таким же, как знак результата); иначе сбра-
сывается.
С: Сбрасывается, если был перенос из бита 15; иначе
устанавливается.
Описание: Вычитает содержимое источника из содержимого прием-
ника и помещает результат в приемник. Первоначальное
содержимое приемника теряется; содержимое источни-
ка не изменяется.
SWAB
SXT
TRAP
ОБМЕНЯТЬ БАЙТЫ ПРИЕМНИКА 0003DD
Двоичный код: Операция: Формат ассемблера: Коды условий: Описание: 0 000 000 011 ddd ddd Старший байт dst, младший байт dst <- младший байт dst, старший байт dst. SWAB dst N: Устанавливается, если с (dst) было < 0; иначе сбра- сывается. V: Сбрасывается. С: Сбрасывается. Меняет местами старший и младший байты слова прием- ника.
РАСШИРИТЬ ЗНАК ПРИЕМНИКА 0067DD
Двоичный код: Операция: Формат ассемблера: Коды условий: Описание: Комментарий: 0 000 110 111 ddd ddd dst *- 0, если с (N) = 0; dst *- 1, если с (N) - 1. SXT dst N: Не изменяется. Z : Устанавливается, если C(N) = 0; иначе сбрасыва- ется. V •. Сбрасывается. С: Не изменяется. Заполняет слово приемника текущим содержимым би- та N. На некоторых моделях ЭВМ PDP-11 эта инструкция не реализована.
ПЕРЕЙТИ К ЛОВУШКЕ 104400 - 104777
Двоичный код: Операция: 1 000 100 Inn ппп ппп 1 (SP) *-c(PSW) HSP)-c(PC) PC *- с(34) PSW*-с (36)
344
Формат ассемблера: TRAPAWjV
Коды условий: Загружаются из второго слова TRAP-вектора.
Описание: Выполняет переход к ловушке через вектор в (фикси-
рованных) ячейках памяти 000034 - 000036.
Младший байт инструкции декодером ЦП не использует-
ся. Однако ассемблер ЭВМ PDP-11 поместит в младшем
байте ассемблированной инструкции любое число NNN,
появляющееся как операнд (0 < NNN < 377), после че-
го этот байт может быть доступен программе пользова-
теля.
TST ПРОВЕРИТЬ ПРИЕМНИК 0057DD
Двоичный код: 0 000 101 111 ddd ddd
Операция: dst <- с(dst).
Формат ассемблера: TST dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Сбрасывается.
Описание: Устанавливает коды условий N и Z в соответствии с со-
держимым слова приемника.
TSTB ПРОВЕРИТЬ ПРИЕМНИК-БАЙТ 1057DD
Двоичный код: 1 000 101 111 ddd ddd
Операция: dst *- c(dst).
Формат ассемблера: TSTB dst
Коды условий: N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Сбрасывается.
Описание: Устанавливает коды условий N и Z в соответствии с со-
держимым байта приемника.
WAIT ОЖИДАТЬ ПРЕРЫВАНИЯ 000001
Двоичный код : 0 000 000 000 000 001
Операция: Приостановить работу процессора и ожидать прерывания.
Формат ассемблера: WAIT
Коды условий: Не изменяются.
Описание: Процессор отказывается от управления шинами (т. е.
прекращает извлечения инструкций) до тех пор, пока не
произойдет внешнее прерывание. Если запрос на преры-
вание будет удовлетворен (что зависит от приоритетов),
то последующая инструкция RTI в подпрограмме обра-
ботки прерываний возобновит выполнение с инструк-
ции, следующей за инструкцией WAIT.
XOR ВЫПОЛНИТЬ ИСКЛЮЧАЮЩЕЕ ИЛИ НАД ПРИЕМНИКОМ 074RDD
И РЕГИСТРОМ
Двоичный код: 0 111 100 rrr ddd ddd
Операция: dst ♦- c(reg) у c(dst).
12 Зак. 2212 345
Формат ассемблера:
Коды условий:
Описание:
Комментарий:
XOR reg, dst
N: Устанавливается, если результат < 0; иначе сбрасы-
вается.
Z : Устанавливается, если результат = 0; иначе сбрасы-
вается.
V: Сбрасывается.
С: Не изменяется.
Выполняется операция ИСКЛЮЧАЮЩЕЕ ИЛИ над со-
держимым регистра и содержимым приемника, и резуль-
тат помещается в слово приемника. Содержимое регист-
ра не изменяется.
На некоторых моделях ЭВМ PDP-11 эта инструкция не
реализована.
А. 3. НАБОР ИНСТРУКЦИЙ В ЧИСЛОВОМ ПОРЯДКЕ ОПЕРАЦИОННОГО КОДА
Операцион- ный код Мнемоника Операцион- ный код Мнемоника Операцион- ный код Мнемоника
000000 HALT 005 ODD CLR 1020XXX BVC
000001 WAIT 0051DD COM Ю24ххх BVS
000002 RTI 005 2DD INC jBCC
юзоххх < или
000003 ВРТ 005 3DD DEC LBHIS
000004 ют 005 4bD NEG 1 MAYYY (bcs
000005 RESET 005 5DD ADC 1 v л Л [BLO ИПИ
000006 RTT* 0056DD SBC 104000
0001DD JMP 005 7DD TST До EMT
000207? RTS 0060DD ROR 104377
00023.V SPL* 0061DD ROL 104400 ]
000240 NOP 006 2DD ASR ДО TRAP
000241 CLC 006 3DD ASL 104777 J
000242 CLV 0064AW MARK* 105 ODD CLRB
000244 CLZ 0067DD SXT* 1051DD COMB
000250 CLN 01SSDD MOV 1O52DD INCB
000257 CCC 02SSDD CMP 1O53DD DECB
000261 SEC 03SSDD BIT 1054DD NEGB
000262 SEV 04SSDD BIC 1055DD ADCB
000264 SEZ 05SSDD BIS 1O56DD SBCB
000270 SEN 06SSDD ADD 105 7DD TSTB
000277 see 070RSS MUL* 1060DD RORB
0003DD SWAB 071RSS DIV* 1061DD RO LB
0004ХХХ BR 072RSS ASH* 1062DD ASRB
ООЮУХУ BNE O73RSS ASHC* 1063DD AS LB
0014XXJT BEQ 074RDD XOR* 1\SSDD MOVB
0020ХХХ BGE O77RXX SOB* \2SSDD CMPB
0024ХХХ В LT Ю00ХХХ BPL 13SSDD BITB
оозоххх BGT 1004XXX BMI 14SSDD BICB
0034ХХХ BLE 1010XXX BHI 15SSDD BISB
004RDD JSR 1014XXX В LOS 16SSDD SUB
* На некоторых моделях ЭВМ PDP-11 эта инструкция не реализована.
Замечание. Коды, не включенные в этот список, либо не распределены, либо соответ-
ствуют инструкциям специального назначения, которые в этом тексте не использо-
вались.
ПРИЛОЖЕНИЕ Б. МАКРОИНСТРУКЦИИ ВВОДА-ВЫВОДА
Б.1. ВВЕДЕНИЕ
Ввод и вывод в образцах программ в этой книге выполняется с помощью макроин-
струкций, представляющих собой последовательности инструкций языка ассемблера
ЭВМ PDP-11, собранных под определенным именем. При ассемблировании в листинге
программы распечатываются только имена макроинструкций, а последовательности со-
ставляющих их инструкций обычно не показываются. Подробности формирования ма-
кроинструкций смотрите в гл. 13.
Для содержательного программирования ввод и вывод весьма существенны. Однако
фактическое программирование для устройств включает управление этими устройства-
ми, необходимое для обеспечения взаимодействия с ними программиста, а также необ-
ходимые преобразования форматов данных. Это достаточно сложные задачи, чтобы
предполагать, что новичок в программировании на языке ассемблера в состоянии с ни-
ми справиться. Поэтому мы даем эти макроинструкции как средства для обеспечения
ввода с клавиатуры и вывода на терминал, когда о самом этом устройстве и программи-
ровании для него требуются лишь самые минимальные знания.
Формат чисел, выводимых макроинструкциями, фиксирован .(это относится и к
формату ввода, но в меньшей степени. См. разд. Б4 и Б5). Вследствие этого програм-
мист не располагает полным управлением над тем видом, в котором появляются вход-
ные и выходные данные. Мы претендуем лишь на то, что эти форматы разумные и что
такие макроинструкции дают возможность легко пользоваться вводом и выводом. Маг
кроопределения показаны в разд. Б.7. В разд. Б.8 содержится листинг основной под
программы, вызываемой макроинструкциями, $...., в которую включены также под-
программы для необходимых преобразований форматов данных. Опытный програм-
мист может модифицировать эти подпрограммы для получения дополнительной гибко-
сти управления форматами, в частности в подпрограммах вывода.
Б.2. ВЫЗОВ МАКРОИНСТРУКЦИЙ ВВОДА-ВЫВОДА
Каждая макроинструкция, используемая для ввода или вывода, вызывается коман-
дой вида
Sdir.frnt adr, ent
где:
dir — направление пересылки данных, которое имеет значение IN (пересылка данных
в программу) или OUT (пересылка данных из программы);
fmt - формат пересылаемых данных. Этот параметр может принимать значение ОСТ
{восьмеричный}, DEC (десятичный), BIN (двоичный, используемый только для
вывода) и ASC (в коде ASCII - для чтения и распечатки знаковых строк);
adr - адрес первого слова или байта подлежащих пересылке данных; пересылка про-
исходит в последовательные ячейки памяти или из последовательных ячеек;
ent - счетчик слов или байтов, подлежащих пересылке. (Для макроинструкций вво-
да и вывода информации в коде ASCII этот счетчик не требуется. См. разд, Б.6.)
Макроинструкции ввода-вывода таковы:
SIN. OCT adr, ent (вводить в восьмеричном виде)
SOUT.OCT adr, ent (выводить в восьмеричном виде)
SIN. DEC adr, ent (вводить в десятичном виде)
SOUT.DEC adr, ent .(выводить в десятичном виде)
SIN. ASC adr [, ent] (вводить в коде ASCII)
SOUT.ASC adr [, c/irj (выводить в коде ASCII)
SOUT.BIN adr, ent (выводить в двоичном виде)
12*
347
346
Передаваемые макроинструкциям аргументы adr и ent должны использоваться с опреде-
ленным вниманием. Если adr - это абсолютный адрес первого слова или байта, подлежа-
щего передаче (например, ячейка памяти с символьным адресом BUFFER), то такой ар-
гумент должен передаваться как абсолютное число (например, #BUFFER). Аналогично,
абсолютный счетчик ввода-вывода (например, 6) должен передаваться как абсолютное
число (#6). Если при вызове макроинструкции выполняются следующие две инструк-
ции:
MOV adr, А
MOV ent, С
то можно легко определить, какие будут передаваться адрес и счетчик. Тогда адресом,
используемым макроинструкцией в качестве адреса первого слова или байта, подлежа-
щего передаче в память или из нее, будет с (А), а счетчиком данных будет с (С). Таким
образом, например, макроинструкция
$IN.DEC #BLOCK, #6
будет пересылать шесть чисел, полагая их в десятичном формате, в шесть последова-
тельных слов, начиная с символьного адреса BLOCK, поскольку после инструкций
MOV #BLOCK, А и MOV #6, С, у нас будет с (А) = BLOCK и с (С) = 6, адрес и счетчик
соответственно. Аналогично последовательность
MOV # BLOCK, R4
MOV #6, TEMP
MOV # TEMP, R2
SOUT.OCT R4, (R2)+
распечатает (в восьмеричном формате) шесть последовательных чисел, начиная с адреса
BLOCK и при этом автоматически увеличит содержимое R2 на 2. Наконец, заметим, что
запись
SOUT.DEC BLOCK, 6
это, по всей видимости, неправильное применение макроинструкции, поскольку она
использует с (BLOCK) в качестве адреса и с (6) в качестве счетчика.
Хотя определения макроинструкций обеспечивают изрядную степень гибкости при
указании адреса и счетчика данных, программиста стоит предостеречь от использования
регистра R6 — указателя стека для удержания адреса. Как видно из листинга макроин-
струкций в разд. Б.7, при вызове макроинструкции в стек проталкивается число эле-
ментов. К тому моменту, когда макроинструкция осуществляет доступ к адресу, ука-
затель стека уже оказывается модифицированным. В то же время счетчик является пер-
вым кандидатом для проталкивания и, следовательно, его с успехом можно передавать *
через SP.
Б.З. ОБНАРУЖЕНИЕ ОШИБОК
Макроинструкции ввода-вывода выполняют проверки на наличие потенциальных
ошибок. Адрес adr первого слова, подлежащего пересылке, должен быть четным, т. е.
должен быть пословным адресом. Если это не так, то распечатывается сообщение об
ошибке недопустимого адреса буфера с указанием значения PC пользователя в месте
ошибки: 7INVALID BUFFER ADDR. IN I/O MACRO INVOKED AT USER PC-XXXXXX.
Это считается фатальной ошибкой, и управление возвращается в операционную систему.
Макроинструкции SIN.ASC и SOUT.ASC в этом отношении являются исключениями,
поскольку адрес, используемый как адрес байта, может быть нечетным. Если счетчик
ent неположительный, то распечатывается сообщение о фатальной ошибке недопустимо-
го счетчика буфера:
? INVALID BUFFER COUNT IN I/O MACRO
INVOKED AT USER PC-XXXXXX.
348
Подпрограммы ввода $IN.OCT и $IN.DEC также выполняют проверку правильно-
сти ввода. Например, число 17294 будет недопустимым вводом для макроинструкции
SIN.OCT (поскольку 9 не является восьмеричной цифрой), не будет им также и число
372040 (поскольку его невозможно поместить в 16 бит). Аналогично, число 32804,
используемое в макроинструкции SIN.DEC, слишком большое десятичное число, что-
бы его можно было разместить в 16 битах. Когда макроинструкция $IN.OCT или
$IN .DEC обнаруживают недопустимое число, она распечатывает следующее сообщение
об ошибке:
% INVALID NUMBER FOR INPUT MACRO
INVOKED AT USER PC-XXXXXX [NNNN]
где NNNN - недопустимое число. Однако фатальной ошибкой это не считается, а неправ
вильное число заменяется нулем и ввод продолжается.
Б.4. ФОРМАТ ВВОДА
Данные,вводимые макроинструкциями SIN.OCT и SIN.DEC, состоят из чисел, раз-
деленных запятыми. Формат данных свободен в том отношении, что пробелы, знаки та-
буляции и лидирующие нули игнорируются. Макроинструкция SIN.DEC допускает ис-
пользование перед числом знаков плюс или минус. Пустое поле трактуется как число 0.
Таким образом, если в качестве ввода для макроинструкции SIN.OCT или $IN. DEC ис-
пользуются числа
1 ,, 3,
то они будут трактоваться, как если бы это было
1, 0,3,0
а
Д ,3
будет рассматриваться как
0,1,3
Б.5. ФОРМАТ ВЫВОДА
Вывод, выполняемый макроинструкцией SOUT.OCT, содержит до девяти чисел на
строке. Если распечатке подлежат больше девяти чисел, то они переносятся на следую-
щую строку. Каждое восьмеричное число выводится длиной шесть цифр вместе с лиди-
рующими нулями.
Вывод, выполняемый макроинструкцией SOUT.DEC, содержит до девяти чисел
на строке. Если подлежат распечатке больше девяти чисел, то вывод переносится на сле-
дующую строку. Каждое десятичное число, если необходимо, снабжается лидирующими
пробелами, а впереди отрицательных чисел ставится знак минус.
Вывод, выполняемый макроинструкцией SOUT.BIN, содержит до четырех чисел на
строке. Если подлежат распечатке больше четырех чисел, то вывод переносится на сле-
дующую строку. Каждое двоичное число содержит 16 двоичных цифр.
Например, если числа (показанные здесь в восьмеричном виде) 304, 552, 172303,
100000, 4, 0, 100001, 177777, 1232, 104401 и 2274 распечатываются при использовании
макроинструкции $OUT. ОСТ, то вывод будет таким:
000304 000552 172303 100000 000004 000000100001 177777 001232
104401 002274
Макроинструкция $OUT.DEC даст такой вывод:
196 362 -2877 -32768 4 0 -32767 -1 666
-30463 1212
349
тогда как макроинструкция SOUT.BIN сгенерирует следующее:
0000000011000100 0000000101101010 1111010011000011 1000000000000000
0000000000000100 0000000000000000 1000000000000001 1111111111111111
0000001010011010 1000100100000001 0000010010111100
Б.6. СПЕЦИАЛЬНЫЙ СЛУЧАЙ - МАКРОИНСТРУКЦИИ $IN. ASC И $OUT.ASC
Макроинструкции $IN.ASC и $OUT.ASC позволяют осуществлять ввод и вывод
знаковых строке коде ASCII. Знаки табуляции, пробелы и управляющие знаки не уда-
ляются. Если задается счетчик знаков, то он должен быть положительным. Адрес перво-
го байта может быть нечетным, поскольку коды для знаков размещаются по байтовым
адресам.
Ни для макроинструкции SIN.ASC, ни для макроинструкции $OUT.ASC счетчик
знаков не требуется. Если в макроинструкции $IN. ASC не задан счетчик байтов, то пе-
ресылка байтов кода ASCII будет завершаться при обнаружении терминатора строки
(знака возврата каретки). После этого макроинструкция SIN.ASC добавит к блоку
только что введенных кодов дополнительный байт, содержащий NUL (000). Таким об-
разом, макроинструкция SIN.ASC без счетчика знаков совместима с директивой
.ASCIZ.
Если в макроинструкции $OUT.ASC не задается счетчик знаков, то распечатка нач-
нется с байта, адрес которого задан, и закончится при обнаружении байта NUL (000). Та-
ким образом,- если, например, ответом на макроинструкцию $IN.ASC #ТЕХТ была
строка MADE IN USA, то это было бы полностью эквивалентно записи
TEXT: .ASCIZ #MADE IN USA#
что можно было бы распечатать затем командой $OUT. ASC #ТЕХТ.
Б.7. ОПРЕДЕЛЕНИЯ МАКРОИНСТРУКЦИЙ ВВОДА-ВЫВОДА
На рис. Б.1 представлены определения макроинструкций ввода-вывода. Содержащий
их файл может использоваться в качестве ’’пролога” к исходным файлам, требующим
использования этих макроинструкций.
.NLIST
MUST НЕ
;*************************************************************;
" ?
? S Y S М А С ?
7 — — — — — — •
; ОПРЕДЕЛЕНИЯ МАКРОСА ’«EXIT’ И СИСТЕМНЫХ МАКРОСОВ ВВОДА- ?
; ВЫВОДА» ДОБАВЛЯЕМЫХ В НАЧАЛО ПРОГРАММ НА МАКРОАССЕМБЛЕРЕ ;
; PDP-11 И ОБЕСПЕЧИВАВШИХ СТАНДАРТИЗОВАННЫЙ ВВОД И ВЫВОД. ;
" ?
j»»»»»»**»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»*»»»»»»»;
.GLOBL х.... ;ПОДПРОГРАММА ДЛЯ ВЫПОЛНЕНИЯ
; ФАКТИЧЕСКОГО ВВОДА-ВЫВОДА
.--------------------«EXIT----------------------------
?
?ВОЗВРАЩАЕТ /ПРАВЛЕНИЕ МОНИТОРУ
.MACRO «EXIT
EMT 350
.ENDM
. ---------------- УТИЛИТНЫИ МАКРОС ------------------
} ВЫЗЫВАЕТСЯ СИСТЕМНЫМИ МАКРОСАМИ
350
.MACRO КОВЕ»BUFFERtKOUNT
МСЛЕДУВШИМИ ДВУМЯ ИНСТРУКЦИЯМИ ДОМНЫ БЫТЬ: MOV PC,-6(SP).
; ПОКАЗАННАЯ КОНСТРУКЦИЯ ПРЕДНАЗНАЧЕНА ДЛЯ ОБЕСПЕЧЕНИЯ СОВМЕСТИМОСТИ
I С АССЕБЛЕРАМИ ВСЕХ МОДЕЛЕЙ PDP-11)
MOV PC,-(SP> ПОЛУЧИТЬ PC ОСНОВНОЙ ПРОГРАММЫ ; ПОЛЬЗОВАТЕЛЯ
MOV (SP)+,-6<SP) ;ДЛЯ СООБЩЕНИЯ ОБ ОШИБКАХ
• IF MOV NB KOUNT KOUNT,-(SP) }ПОМЕСТИТЬ СЧЕТЧИК В СТЕК
MOV #1,-<SP) ;УСТАНОВИТЬ ФЛАГ ’СЧЕТЧИК УКАЗАН’
.IFF CLR -<SP) ;ПОМЕСТИТЬ В СТЕК ’НУЛЕВОЙ СЧЕТЧИК’
CLR -<SP) IСБРОСИТЬ ФЛАГ ’СЧЕТЧИК УКАЗАН’
.ENDC TST - <SP> ;ОБОИТИ PC ПОЛЬЗОВАТЕЛЯ
MOV BUFFER,-(SP) »ПОМЕСТИТЬ АДРЕС БУФЕРА
MOV t’K0DE,-(SP> 5 И КОД В СТЕК
JSR PC,*.... !ПЕРЕЙТИ К ВЫПОЛНЕНИЮ ВВОДА-ВЫВОДА
.ENDM - - СИСТЕМНЫЕ МАКРОСЫ ВВОДА-ВЫВОДА *
.MACRO XIN.OCT BUFFERtCOUNT
к.. О»BUFFER,COUNT
.ENDM
.MACRO xIN.DEC BUFFERtCOUNT
x.. 2»BUFFER,COUNT
.ENDM
.MACRO xQUT.OCT BUFFER,COUNT
».. 4,BUFFER,COUNT
.ENDM
.MACRO xOUT.DEC BUFFER,COUNT
x.. 6,BUFFER,COUNT
.ENDM
.MACRO xOUT.BIN BUFFERtCOUNT
x.. 10,BUFFER,COUNT
.ENDM
.MACRO xIN.ASC BUFFER,COUNT
x.. 12,BUFFER,COUNT
i ENDM
.MACRO xOUT.ASC BUFFERtCOUNT
x.. 14,BUFFER,COUNT
.ENDM
’list TTM
.LISTT
Рис. Б.1
Б.8. ПОДПРОГРАММА ВВОДА-ВЫВОДА
На рис. Б.2 показан листинг подпрограммы которая вызывается макроин-
струкциями ввода-вывода. Эта подпрограмма выполняет действительный ввод и вывод
и осуществляет все необходимые преобразования данных.
351
1 -TITLE MACLIB
2
3 ?**************************************************;
4
5 MACLIB 1
6 • ~ ~ .
7 ; ПОДПРОГРАММЫ ВВОДА-ВЫВОДА' НАПИСАННЫЕ НА {
8 { МАКРОАССЕМБЛЕРЕ PDP-11. ВЫЗЫВАЮТСЯ МАКРОСАМИ» !
9 { ОПРЕДЕЛЕННЫМИ В ФАЙЛЕ ’SYSMAC’ ;
10 ; »
И ; ТОЧКА ВХОДА: ж.... ;
12 ; ;
13 ; используется ПОДПРОГРАММЫ ВВОДА-ВЫВОДА СТРОК СИС-{
14 { ТЕМЫ RT-11, ВЫЗЫВАЕМЫЕ ЧЕРЕЗ ЕМТ 340 И ЕМТ 351 {
15 г 9
16 ; ************************************************** |
17
18 J**************************************************?
19 » 3
20 { БУФЕРЫ И ФЛАГИ ;
21 ? »
22 ;**************************************************;
23
24 OOOOOO BUFFER: .BLKB 81. {ВНУТРЕННИЙ БУФЕР ВВОДА-ВЫВОДА
25 000121 BFLAG: .BLKB 1 {ФЛАГ ЗАИИТЫ БУФЕРА
26 000122 000 CRLF: .BYTE 0 {ВОЗВРАТ КАРЕТКИ И ПЕРЕВОД СТРОКИ
27 000123 SELECT: .BLKB 1 {ФЛАГ ВЫБОРА ПОДПРОГРАММЫ
28
29 000040 BLANK =- 40 {ПРОБЕЛ В КОДЕ ASCII
30 000015 CARRET = 15 {ВОЗВРАТ КАРЕТКИ В КОДЕ ASCII
31
32 J**************************************************;
33 ; {
34 { ВЕКТОР» ИСПОЛЬЗУЕМЫМ ДЛЯ ПЕРЕДАЧИ УПРАВЛЕНИЯ {
35 { К СООТВЕТСТВУЮЩЕЙ ПОДПРОГРАММЕ ВВОДА-ВЫВОДА {
36 ? р
37 ?**************************************************?
38
39 000124 001660’ XFER: OREAD {ЧИТАТЬ ВОСЬМЕРИЧНЫЕ ЧИСЛА
40 000126 001666’ DREAD {ЧИТАТЬ ДЕСЯТИЧНЫЕ ЧИСЛА
41 000130 002002’ OPRINT {ПЕЧАТАТЬ ВОСЬМЕРИЧНЫЕ ЧИСЛА
42 000132 002010’ DPRINT {ПЕЧАТАТЬ ДЕСЯТИЧНЫЕ ЧИСЛА
43 000134 002122' BPRINT {ПЕЧАТАТЬ ДВОИЧНЫЕ ЧИСЛА
44 000136 002220’ AREAD {ЧИТАТЬ ТЕКСТ В КОДЕ ASCII
45 000140 002274’ APRINT {ПЕЧАТАТЬ ТЕКСТ В КОДЕ ASCII
46
47 ;«ж************************************************?
48 3 >
49 { ВНУТРЕННИЕ ПОДПРОГРАММЫ {
50 ! •
51 3Л************************************** ***********?
52
53 { ПОДПРОГРАММА ВВОДА RT-11
54 {
55 {ПРЕДПОЛАГАЕТСЯ» ЧТО АДРЕС БУФЕРА НАХОДИТСЯ В R1
56
57 000142 104340 INPSTR: ЕМТ 340 {ПОЛУЧИТЬ ЗНАК
58 000144 103776 BCS INPSTR {ПОВТОРИТЬ, ЕСЛИ ОЮИБКА
59 000146 120027 СМРВ RO,#CARRET {ВОЗВРАТ КАРЕТКИ?
000015
60 000152 001410 BEQ INP020 {ДА — СДЕЛАНО
61 000154 105767 TSTB BFLAG {БУФЕР ЗАИИКЕН?
177741
62 000160 001403 BEQ INP010 {НЕ ТРОГАТЬ
63 0001-62 020127 СМР Rl,tBUFFER+80. {БУФЕР ПОЛОН?
000120’
64 000166 001765 BEQ INPSTR {ДА — ИГНОРИРОВАТЬ ЭТОТ ЗНАК
65 000170 110021 INP010: MOVB RO,(R1) + {ПОМЕСТИТЬ В БУФЕР
66 000172 000763 BR INPSTR { И ПОЛУЧИТЬ ДРУГОЙ
67 000174 105011 INP020: CLRB (R1) {ВСТАВИТЬ НУЛЕВОЙ БАЙТ
68 000176 104340 ЕМТ 340 {ВЫВЕСТИ ПЕРЕВОД СТРОКИ
69 000200 000207 RTS PC { И ВЕРНУТЬСЯ
70
71 { ПОДПРОГРАММА ВЫВОДА RT-11
72
73 000202 012700 PRINTX: MOV tBUFFER.RO {ПРЕДПОЛАГАЕМЫЙ АДРЕС БУФЕРА
OOOOOO’
352
74 000706 010046 PRINT: MOV RO.-(SP) ^СОХРАНИТЬ АДРЕС БУФЕРА
75 000210 012700 MOV SCRLF.RO ;ПОСЛАТЬ ВОЗВРАТ КАРЕТКИ И
000122’
76 000214 104351 EMT 351 ; ПЕРЕВОД СТРОКИ
77 000216 012600 MOV (SP)+.RO ВОССТАНОВИТЬ АДРЕС БУФЕРА
78 000220 104351 EMT 351 ВЫЗВАТЬ ПОДПРОГРАММУ RT-11
79 000222 000207 RTS PC ; И ВЕРНУТЬСЯ
80
81 - r УПАКОВКА БУФЕРА
82 • f
83 ; УДАЛЯЕТ ПРОБЕЛЫ И УПРАВЛЯВШИЕ КОДЫ ИЗ
84 : ВНУТРЕННЕГО БУФЕРА
85
86 000224 012703 PACK: MOV SBUFFER.R3 ОСТАНОВИТЬ УКАЗАТЕЛИ ’ПРОСМОТРА’
OOOOOO’
87 000230 010304 MOV R3.R4 1 И ’ВСТАВКИ’
88 000232 105713 PACK20: TSTB <R3) ;ПРОВЕРИТЬ ЗНАК
89 000234 001406 BEO PACK80 (НОЛЬ — КОНЕЦ БУФЕРА
90 000236 122327 CMPB (R3)+.*BLANK ;ПРОБЕЛ ИЛИ УПРАВЛЕНИЕ?
000040
91 000242 003773 BLE PACK20 »ДА — ИГНОРИРОВАТЬ ЕГО
92 000244 116324 MOVB -1(R3)»(R4) + ;ПОМЕСТИТЬ В БУФЕР
177777
93 000250 000770 BR РАСК20 ; И ВЗЯТЬ СЛЕДУЮЩИЙ ЗНАК
94 000252 105014 PACK80: CLRB (R4) ВОССТАНОВИТЬ КАК КОНЕЦ БУФЕРА
95 000254 000207 RTS PC ; И ВЕРНУТЬСЯ
96 r
97 ПРОСМОТР буфера
98 • f
99 ; ИЩЕТ РАЗГРАНИЧИТЕЛЬ В БУФЕРЕ (ИЛИ НУЛЕВОЙ БАЙТ).
100 : R3 СОДЕРЖИТ АДРЕС ПОСЛЕДНЕГО РАЗГРАНИЧИТЕЛЯ
101 1
102 000256 005203 SCAN: INC R3 ;ПЕРЕДВИНУТЬ УКАЗАТЕЛЬ
103 000260 010300 MOV R3.R0 ; И УСТАНОВИТЬ УКАЗАТЕЛЬ СТРОКИ
104 000262 121327 SCAN10: CMPB (R3),4’. ;ЗАПЯТАЯ?
000054
105 000266 001403 BEQ SCAN40 ;ДА — НАЙТИ ЕЕ
106 000270 105723 TSTB <R3) + ;НУЛЕВОЙ БАЙТ?
107 000272 001373 BNE SCAN10 ;НЕТ — ПРОДОЛЖИТЬ ПРОСМОТР
108 000274 005303 DEC R3 ;ПЕРЕДВИНУТЬ УКАЗАТЕЛЬ НАЗАД
109 000276 000207 SCAN40: RTS PC ;ВОЗВРАТ
110 >
111 --02BIN
112 f
113 ; ПРЕОБРАЗОВЫВАЕТ ВОСЬМЕРИЧНУЮ СТРОКУ В КОДЕ ASCII В ДВОИЧНЫЙ ВИД.
114 ; ro УКАЗЫВАЕТ НА 1-й ЗНАК СТРОКИ
115 ; R3 УКАЗЫВАЕТ НА ЗАВЕРШАЮЩИЙ РАЗГРАНИЧИТЕЛЬ (ИЛИ НУЛЕВОЙ БАЙТ).
116 ; ПРЕОБРАЗОВАННОЕ ЧИСЛО ВОЗВРАЩАЕТСЯ В R5
117 ; ЕСЛИ УСТАНОВЛЕН БИТ ПЕРЕНОСА. ТО БЫЛА ОШИБКА В ПРЕОБРАЗОВАНИИ
118
119 000300 010046 02BIN: MOV RO.-(SP) ;(ДЛЯ СООБЩЕНИЯ ОБ ОШИБКЕ)
120 000302 010146 MOV Rl.-(SP) СОХРАНИТЬ УКАЗАТЕЛЬ БУФЕРА ПОЛЬЗОВАТЕЛЯ
121 000304 122027 02B100: CMPB (R0>+.4’0 5ИГНОРИРОВАТЬ
000060
122 000310 001775 BEO 02В100 ; ЛИДИРУЮЩИЕ НУЛИ
123 000312 005300 DEC RO ВОССТАНОВИТЬ УКАЗАТЕЛЬ
124 000314 005005 CLR R5 ВЧИСТИТЬ ДЛЯ СДВИГА
125 000316 020003 02B200: CMP “R0.R3 ;КОНЕЦ СТРОКИ?
126 000320 001420 BEQ 02B600 ;да — обработать
127 000322 112001 MOVB (R01+.R1 ВОЛУЧИТЬ ЗНАК
128 000324 162701 SUB S’O.Rl ; И СДЕЛАТЬ ДВОИЧНЫМ
000060
129 000330 020127 CMP R1.47 ВОСЬМЕРИЧНОЕ?
000007
130 000334 101010 BHI 02B500 ВЕТ — ОШИБКА
131 000336 006305 ASL R5 ;УМНОЖИТЬ НА 2
132 000340 103406 BCS 02B500 ;ОШИБКА» ЕСЛИ ПЕРЕНОС
133 000342 006305 ASL R5 ;УМНОЖИТЬ НА 2
134 000344 103404 BCS 02B500 ;ОШИБКА. ЕСЛИ ПЕРЕНОС
135 000346 006305 ASL R5 ;УМНОЖИТЬ НА 2
136 000350 103402 BCS 02B500 ;ОШИБКА. ЕСЛИ ПЕРЕНОС
137 000352 050105 BIS R1.R5 ;ЗАНЕСТИ ПОСЛЕДНИЕ 3 БИТА
138 000354 000760 BR O2B2OO ; И ПОЛУЧИТЬ СЛЕДУЮЩЕЕ ЧИСЛО
139 000356 000261 02B500: SEC ;УСТАНОВИТЬ ФЛАГ ОШИБКИ
140 000360 000401 BR 02B600+2 ; И ОБОЙТИ
141 000362 000241 02B600: CLC ;ОЧИСТИТЬ ФЛАГ ОШИБКИ
353
142 000364 012601 MOV (SP)+.R1 {ВОССТАНОВИТЬ
143 000366 012600 MOV (SP)+,RO { РЕГИСТРЫ
144 000370 000207 RTS PC { И ВЕРНУТЬСЯ
145 r
146 D 2BIN
147 1
148 ; ПРЕОБРАЗОВЫВАЕТ ДЕСЯТИЧНУЮ СТРОКУ В КОДЕ ASCII В ДВОИЧНУЮ
149 ; R0 УКАЗЫВАЕТ НА 1-И ЗНАК СТРОКИ
150 ; R3 УКАЗЫВАЕТ НА ЗАВЕРШАЮЩИЙ РАЗГРАНИЧИТЕЛЬ (ИЛИ НУЛЕВОЙ БАЙТ)
151 : ПРЕОБРАЗОВАННОЕ ЧИСЛО ВОЗВРАЩАЕТСЯ В R5
152 ; ЕСЛИ УСТАНОВЛЕН БИТ ПЕРЕНОСА. ТО БЫЛА ОШИБКА В ПРЕОБРАЗОВАНИИ
153
154 000372 105067 D2BIN: CLRB D2B800 {ЗАМЕНИТЬ ’BR’ НА ПУСТУО ОПЕРАЦИЮ
000166
155 000376 010046 MOV RO.-(SP) {СОХРАНИТЬ.
156 000400 010146 MOV Rl.-(SP) { РАБОЧИЕ
157 000402 010346 MOV R3.-(SP) ; РЕГИСТРЫ
158 000404 005004 CLR R4 {ПРЕОБРАЗОВАННОЕ ЧИСЛО
159 000406 005001 CLR Rl {СТЕПЕНИ ДЕСЯТИ
160 000410 121027 CMPB (RO) {ОТРИЦАТЕЛЬНОЕ?
000055
161 000414 001002 BNE D2B100 (НЕТ
162 000416 005200 INC RO {ОБОЙТИ
163 000420 000406 BR D2B200 { И ПРОВЕРИТЬ НУЛИ
164 000422 105267 D2B100: INCB D2B800 (ПРОПУСТИТЬ одно слово
000136
165 000426 121027 CMPB (RO).*’♦ (ЗНАК ПЛЮС?
000053
166 000432 001001 BNE D2B200 {НЕТ — ОБОЙТИ.
167 000434 005200 INC RO { ИНАЧЕ ОБОЙТИ ’+’
168 000436 122027 D2B200: CMPB (RO)+.»’O •ИГНОРИРОВАТЬ
000060
169 000442 001775 BEQ D2B200 { ЛИДИРУЮЩИЕ НУЛИ
170 000444 005300 DEC RO (ВЕРНУТЬ УКАЗАТЕЛЬ
171 000446 020003 D2B240S CMP R0.R3 (СДЕЛАНО?
172 000450 001444 BEQ D2B780 (ДА — ПРОПУСТИТЬ
173 000452 114305 MOVB -(R3).R5 (ПОЛУЧИТЬ ЗНАК
174 000454 162705 SUB *’0.R5 ( И СДЕЛАТЬ ДВОИЧНЫМ
000060
175 000460 001425 BEQ D2B260+2 {0 — НЕ ТРЕБУЕТСЯ УМНОЖЕНИЕ
176 000462 002435 BLT D2B760 (НЕ ДЕСЯТИЧНОЕ
177 000464 020527 CMP R5.*9. (ВЕРНОЕ ДЕСЯТИЧНОЕ?
000011
178 000470 101032 BHI D2B760 {НЕТ — ОШИБКА
179 000472 010146 MOV Rl.-(SP) (СОХРАНИТЬ СЧЕТЧИК
180 000474 001416 BEQ D2B260 (ВЫПОЛНИТЬ. ЕСЛИ НОЛЬ
181 000476 006305 D2B245: ASL R5 (УМНОЖИТЬ НА 2
182 000500 100406 BHI D2B250+2 (ОШИБКА. ЕСЛИ ПЕРЕПОЛНЕНИЕ.
183 000502 010546 MOV R5.-(SP) ( ИНАЧЕ R5 В СТЕК
184 000504 006305 ASL R5 (УМНОЖИТЬ НА 2
185 000506 100402 BMI D2B250 (ОШИБКА. ЕСЛИ ПЕРЕПОЛНЕНИЕ
186 000510 006305 ASL R5 (УМНОЖИТЬ НА 2
187 000512 100003 BPL D2B255 (ХОРОШО. ЕСЛИ НЕТ ПЕРЕПОЛНЕНИЯ
188 000514 005726 D2B250: TST (SP) + (ПОДСТРОИТЬ SP НА 2
189 000516 005726 TST (SP) + (ПОДСТРОИТЬ SP НА 2
190 000520 000416 BR D2B760 (УСТАНОВИТЬ ОШИБКУ
191 000522 062605 D2B255: ADD (SP)+,R5 (УМНОЖИТЬ НА 10.
192 000524 102774 BVS D2B250+2 (ОВИБКА. ЕСЛИ ПЕРЕПОЛНЕНИЕ.
193 000526 005301 DEC Rl ( ИНАЧЕ УМЕНЬШИТЬ СЧЕТЧИК
194 000530 001362 BNE D2B245 1 И СНОВА УМНОЖИТЬ
195
196 000532 012601 D2B260: MOV (SP)+jR1 (ВОССТАНОВИТЬ R1
197 000534 005201 INC Rl 1 И УВЕЛИЧИТЬ
198 000536 060504 ADD R5.R4 (НАКОПИТЬ
199 000540 102342 BVC D2B240 (НЕТ ПЕРЕПОЛНЕНИЯ — ХОРОШО
200 000542 020427 CMP R4 .*100000 (ВЕРНО ТОЛЬКО ДЛЯ ОТРИЦАТЕЛЬНОГО
100000
201 000546 001003 BNE D2B760 (ОШИБКА
202 000550 105767 TSTB D2B800 (ЧИСЛО ОТРИЦАТЕЛЬНОЕ?
000010
203 000554 001734 BEQ D2B240 {ДА — ХОРОШО
204
205 000556 000261 B2B760t SEC •УСТАНОВИТЬ «ЛАГ ОШИБКИ
206 000560 000404 BR D2B820 ( И ОБОЙТИ
207 000562 010405 B2B780S HOV R4.R5 (ПОМЕСТИТЬ РЕЗУЛЬТАТ В R5
208 000564 000400 D2R800: BR .+2 {(ПРОПУСТИТЬ 1 СЛОВО ИЛИ ПУСТАЯ ОПЕРАЦИЯ)
209 000566 005405 NEG R5 (ИЗМЕНИТЬ ЗНАК. ЕСЛИ ОТРИЦАТЕЛЬНОЕ
354
210 000570 000241 CLC ;ОЧИСТИТЬ ФЛАГ опивки
211 000572 012603 D2B820: MOV (SP)+,R3 .ВОССТАНОВИТЬ
212 000574 012601 MOV (SP)+,R1 ; регистры
213 000576 012600 MOV (SP)+,RO
214 000600 000207 RTS PC ;возврат
215 216 BIN20
217
218 ПРЕОБРАЗОВЫВАЕТ ДВОИЧНОЕ В 6-РАЗРЯДНОЕ ВОСЬМЕРИЧНОЕ
219 ЧИСЛО ПРЕОБРАЗОВЫВАЕТСЯ В R5
220 221 АДРЕС ПЕРВОГО БАЙТА ЗА ПРЕОБРАЗОВАННЫМ ЧИСЛОМ — В R5
222 000602 112710 000040 BIN20: MOVB •BLANK»(RO) ;ПОМЕСТИТЬ
223 000606 112760 MOVB «BLANK»1(RO) ; ДВА ПРОБЕЛА
224 000614 000040 000001 012704 MOV *5»R4 ОСТАНОВИТЬ СЧЕТЧИК
225 000620 000005 010546 MOV R5»-(SP) ;СОХРАНИТЬ ЧИСЛО
226 000622 042705 BIN020: BIC «177770,R5 СТЕРЕТЬ ВСЕ» КРОНЕ ПОСЛЕДНИХ 3 БИТ
227 000626 177770 062705 ADD *’0,R5 ; И СФОРМИРОВАТЬ КОД ASCII
228 000632 000060 110540 MOVB R5»-(R0) ПОМЕСТИТЬ КОД ASCII В БУФЕР
229 000634 012605 MOV (SP)+»R5 ;СНОВА ВЗЯТЬ ЧИСЛО
230 000636 006205 ASR R5 ;сдвинуть
231 000640 006205 ASR R5 ; ВПРАВО
232 000642 006205 ASR R5 ; НА 3 БИТА
233 000644 010546 MOV R5»-(SP) ;снова поместить в стек
234 000646 005304 DEC R4 ?ПОВТОРИТЬ ДДЯ
235 000650 001364 BNE BIN020 ; СЛЕДУЮЩИХ ТРЕХ БИТОВ
236 000652 112740 MOVB S’O»-(RO) ПОМЕСТИТЬ КОД ’0’ В 1-УО ПОЗИЦИИ
237 000656 000060 005726 TST (SP) + СЧИСТИТЬ СТЕК
238 000660 001401 BEQ .+4 ; И ОБОЙТИ, ЕСЛИ 0,
239 000662 105210 INCB (RO) ; ИНАЧЕ УСТАНОВИТЬ 61
240 000664 000207 RTS PC ;ВОЗВРАТ
241 242 243 244 245 246 247 248 000666 112710 BIN2D: . BIN2D ; ПРЕОБРАЗОВЫВАЕТ ДВОИЧНОЕ В 6-РАЗРЯДНОЕ ДЕСЯТИЧНОЕ ; ЧИСЛО ПРЕОБРАЗОВЫВАЕТСЯ В R5 ; АДРЕС ПЕРВОГО БАЙТА ЗА ПРЕОБРАЗОВАННЫМ ЧИСЛОМ — В R5 MOVB «BLANK,(RO) ;ПОМЕСТИТЬ
249 000672 000040 112760 MOVB «BLANK»1(RO) ; ДВА ПРОБЕЛА
250 000700 000040 000001 162700 SUB «6» RO ОСТАНОВИТЬ УКАЗАТЕЛЬ
251 000704 000006 010046 MOV RO»-(SP) ; И СОХРАНИТЬ
252 000706 112720 MOVB «’0,(R0)+ ОСТАНОВИТЬ КОД НУЛЯ
253 000712 000060 112746 MOVB «BLANK»-iSP) ГСОХРАНИТЬ ПРОБЕЛ
254 000716 000040 005705 TST R5 ПРОВЕРИТЬ ЗНАК ЧИСЛА
255 000720 002003 BGE BIN100 ПРОПУСТИТЬ, ЕСЛИ НЕОТРИЦАТЕЛЬНОЕ,
256 000722 005405 NEG R5 ; ИНАЧЕ ИЗМЕНИТЬ ЗНАК
257 000724 112716 MOVB «’-»(SP) ; И ЗАМЕНИТЬ ПРОБЕЛ НА
258 000730 000055 012702 BIN100: MOV «BIN600-2,R2 ОСТАНОВИТЬ АДРЕС ТАБЛИЦЫ
259 000734 001020’ 005004 BIN120: CLR R4 ОСТАНОВИТЬ СЧЕТЧИК
260 000736 005722 TST (R2) + ПЕРЕДВИНУТЬ УКАЗАТЕЛЬ
261 000740 005712 TST (R2) ; И ПРОВЕРИТЬ СТЕПЕНЬ 10.
262 000742 001410 BEQ BIN300 1ЕСЛИ 0 — ОБРАБОТАТЬ
263 000744 005204 BIN200: INC R4 ОВЕЛИЧИТЬ СЧЕТЧИК
264 000746 161205 SUB (R2),R5 ‘ВЫЧЕСТЬ СТЕПЕНЬ 10.
265 000750 100375 BPL BIN200 ;ВЫПОЛНИТЬ СНОВА» ЕСЛИ НЕОТРИЦАТЕЛЬНОЕ
266 000752 061205 ADD (R2),R5 ; ИНАЧЕ ВЕРНУТЬ НАЗАД
267 000754 062704 ADD «’0-1,R4 СФОРМИРОВАТЬ СЧЕТЧИК КОДОВ ASCII
268 000760 000057 110420 MOVB R4,(R0) + ; И ПОМЕСТИТЬ В БУФЕР
269 000762 000764 BR BIN120 ;ОБРАБОТАТЬ СЛЕДУЮЩУЮ СТЕПЕНЬ 10.
355
270 000764 162700 000006 BIN300: SUB 46. RO »ВОССТАНОВИТЬ УКАЗАТЕЛЬ
271 000770 012702 000005 MOV 45. R2 ; И УСТАНОВИТЬ СЧЕТЧИК
272 000774 122710 000060 BIN400: CMPB 4’0.(RO) {ASCII КОД НУЛЯ?
273 001000 001004 BNE BIN500 {НЕТ — ВЫПОЛНЯТЬ ПРОСМОТР.
274 001002 112720 000040 MOVB 4BLANK» (R0) + ; ИНАЧЕ ИЗМЕНИТЬ ’0’ НА ПРОБЕЛ
275 001006 005302 DEC R2 ; И ПРОВЕРИТЬ
276 001010 001371 BNE BIN400 { СЛЕДУВШИИ БАЙТ
277 001012 112660 177777 BIN500: MOVB (SP)+.-l(RO) {ПОМЕСТИТЬ ’-’ ИЛИ ПРОБЕЛ
278 00101,6 012600 MOV (SP)+.RO ;ВОССТАНОВИТЬ УКАЗАТЕЛЬ
279 001020 000207 RTS PC ; и вернуться
280 f
281 tТАБЛИЦА СТЕПЕНЕЙ 10.
282
283 001022 023420 BIN600: .WORD 10000.
284 001024 001750 .WORD 1000.
285 001026 000144 .WORD 100.
286 001030 000012 .WORD 10.
287 001032 000001 .WORD 1.
288 001034 000000 .WORD 0.
289
290 f — — - ОБРАБОТЧИК ОШИБОК
291
292 001036 012701 001234’ ERROR1: MOV 4ERR2.R1 {УСТАНОВИТЬ АДРЕС ОШИБКИ
293 001042 020127 001241’ CMP R1.4ERR2+5 ;СДЕЛАНО?
294 001046 001402 BEQ ERRPC {ДА — УСТАНОВИТЬ PC.
295 001050 112021 MOVB (RO)*» (RD* { ИНАЧЕ ЗАНЕСТИ ЗНАК
296 001052 000773 BR ERR0R1+4 { И ПРОВЕРИТЬ СНОВА
297 001054 012700 001340’ ERRF'C: MOV 4ERR3-3.R0 «УСТАНОВИТЬ ПРЕОБРАЗОВАНИЕ {ПОЛУЧИТЬ PC ПОЛЬЗОВАТЕЛЯ
298 001060 016605 000020 MOV 20(SP),R5
299 001064 005745 TST -(R5) ; И УМЕНЬВИТЬ
300 001066 004767 177510 JSR PC.BIN20 ;ПРЕОБРАЗОВАТЬ
301 001072 012700 001214’ MOV 4ERR1.R0 ;УКАЗАТЬ НА СООБЩЕНИЕ
302 001076 004767 177104 JSR PC.PRINT { И РАСПЕЧАТАТЬ ОШИБКУ
303 001102 012700 000122’ MOV 4CRLF.R0 ;НАПЕЧАТАТЬ
304 001406 104351 EMT 351 ; ДОПОЛНИТЕЛЬНУВ СТРОКУ
305 001110 104350 EMT 350 ;ВЕРНУТЬСЯ В МОНИТОР
306 r
W 001112 308 ERR0R2: .IRP X,(1.2.3)
309 MOV R’X.-(SP) {СОХРАНИТЬ 3 РЕГИСТРА
310 .ENDM
311 001120 012701 001455’ MOV 4ERR5.R1 •УСТАНОВИТЬ УКАЗАТЕЛЬ
312 001124 020003 ER200: CMP R0.R3 ;ОГРАНИЧИТЕЛЬ?
313 001126 001405 BEQ ER220 {ДА — ПРОПУСТИТЬ
314 001130 020127 001464’ CMP R1.SERR5+7 /КОНЕЦ БУФЕРА?
315 001134 001402 BEQ ER220 {ДА — БОЛЬШЕ НЕТ МЕСТА
316 001136 112021 MOVB (RO)+.(R1)+ {ПОМЕСТИТЬ ЗНАК
317 001140 000771 BR ER200 { И ОБРАБОТАТЬ СЛЕДУВШИИ
318 001142 112721 000112 ER220: MOVB 4’J»(R1)+ ;ПОМЕСТИТЬ ’J’
319 001146 105011 CLRB (RD ; И ПРИЗНАК КОНЧА
320 001150 012700 001452’ MOV 4ERR5-3.R0 ;УСТАНОВИТЬ УКАЗАТЕЛЬ
321 001154 016605 000030 MOV 30(SP>»R5 ;ПОЛУЧИТЬ PC ПОЛЬЗОВАТЕЛЯ
322 001160 005745 TST -(R5) ; И УМЕНЬШИТЬ
323 001162 004767 177414 JSR PC.BIN20 ;ПРЕОБРАЗОВАТЬ
324 001166 012700 001343’ MOV 4ERR3.R0 {УСТАНОВИТЬ УКАЗАТЕЛЬ
325 001172 004767 177010 JSR PC.PRINT 1 И РАСПЕЧАТАТЬ ОШИБКУ
356
326 001176 012700 000122’ MOV 6CRLF.R0 .РАСПЕЧАТАТЬ
327 001202 104351 ЕНТ 351 ; ДОПОЛНИТЕЛЬНУЮ СТРОКУ
328 .IRP X.(3,2.1)
329 MOV (SP)+,R’X ВОССТАНОВИТЬ РЕГИСТРЫ
330 .ENDM
331 001212 000207 RTS PC .ВОЗВРАТ
332 • r
333 ;MM****»****»**»***************************#***##*»;
334 • • >
335 r СООБЩЕНИЯ ;
336 • f
337 ;*«***«я********************************«*««««*****;
338 r
339 001214 ERR1: .ASCII 47НЕВЕРНЫИ БУФЕР 4
340 001234 ERR2: .ASCII 4ХХХХХ В МАКРОСЕ ВВОДА-ВЫВОДА ВЫЗЫВАЕТСЯ 4
341 .ASCII 4ПРИ PC ПОЛЬЗОВАТЕЛЯ = 4
342 001332 .ASCIZ 4ХХХХХХ 4
343 001343 ERR3: .ASCII 47НЕВЕРН0Е ЧИСЛО ДЛЯ ВВОДА 4
344 001375 .ASCII 4МАКР0С ВЫЗЫВАЕТСЯ ПРИ PC ПОЛЬЗОВАТЕЛЯ = 4
345 001445 130 ERR4: .ASCII 4ХХХХХХ С4
346 001455 ERR5: .BLKB 9.
347
348 001466 COUNTf .ASCII 4СЧЕТЧИК4
349 001475 ADDR.: .ASCII 4АДР.4
350 .EVEN
351 j**************************************************
352
353 » ТОЧКА ВХОДА
354 r
355 ; АРГУМЕНТЫ ПРИ ВХОДЕ В ПОДПРОГРАММУ — В СТЕКЕ:
356
357 ; SP — ) PC ВОЗВРАТА В ОСНОВНУЮ ПРОГРАММУ
358 Г КОД МАКРОСА ВВОДА-ВЫВОДА
359 1 АДРЕС БУФЕРА
360 г (АДРЕС ВЫЗОВА ИЗ ОСНОВНОЙ ПРОГРАММЫ)+2
361 t •ЛАГ ’СЧЕТЧИК УКАЗАН’
362 f ЧИСЛО ИЛИ СЧЕТЧИК ЗНАКОВ
363 г
364 ;**************************************************
365
366 001502 012667 *....:: MOV (SP)+.RET+2 ОСТАНОВИТЬ АДРЕС ВОЗВРАТА
000122
367 .IRP X,(0,1,2,3,4,5)
368 MOV R’X.-(SP) ;СОХРАНИТЬ RO - R5
369 .ENDM
370 001522 QQ57&6 TST 24(SP) ;ПРОВЕРИТЬ СЧЕТЧИК
000024
371 001526 002410 BLT BADCNT ;ОТРИЦАТЕЛЬНЫМ — ОЮИБКА
372 001530 003013 BGT *00040 :ПОЛОЖИТЕЛЬНЫЙ — ХОРОШО
373 001532 026627 CMP 14(SP),410 ‘СРАВНИТЬ КОД
000014
000010
374 001540 003403 BLE BADCNT ;ОЮИБКА — НЕ КОД ASCII
375 001542 005766 TST 22(SP) ;СЧЕТЧИК УКАЗАН?
000022
376 001546 001431 BEQ *00100 ;НЕТ -- НЕ ДОЛЖНО БЫТЬ
377
378 001550 012700 BADCNT: MOV 4C0UNT.R0 ОСТАНОВИТЬ УКАЗАТЕЛЬ
001466'
379 001554 000167 JMP ERRQR1 ! И ПОСЛАТЬ СООБЩЕНИЕ
177256
380
381 001560 026627 *00040: CMP 14(SP).41O ^ПОДПРОГРАММА ASCII?
000014
000010
382 001566 003021 BGT *00100 ;ДА — ЛЮБОЙ АДРЕС ПРАВИЛЕН
383 001570 032766 BIT 41.16(SP) ;АДРЕС ЧЕТЕН?
000001
000016
384 001576 001415 BEQ *00100 ;ДА — ХОРОИО
357
385 001600 012700 001475’ MOV «ADDR.»RO ;УСТАНОВИТЬ УКАЗАТЕЛЬ
386 001604 000763 BR BADCNT+4 ; И ПОСЛАТЬ СООБЩЕНИЕ
387 7
388 ;**************************************************;
389 9 9
390 ВОЗВРАТ В ВЫЗЫВАЮЩУЮ ПРОГРАММУ 1
391 f г
392 ;**************************************************"
393
394 001606 RETURN:
395 . IRP Х'<5'4'3'2'1'0>
396 NOV (SP)+.R’X ;ВОССТАНОВИТЬ R5 - R0
397 .ENDM
398 001622 062706 ADD «12»SP fУСТАНОВИТЬ УКАЗАТЕЛЬ СТЕКА
000012
399 001626 000137 RET: JMP G«0 .ВОЗВРАТ В ГЛАВНУЮ ПРОГРАММУ
000000
400 ;(ПЕРЕКРЫВАЕТСЯ С АДРЕСОМ ВОЗВРАТА)
401
402 001632 016600 •00100: MOV 14<SP)»R0 ;ПОЛУЧИТЬ КОД ПРОГРАММЫ'
000014
403 001636 016601 MOV 16(SP)»R1 АДРЕС БУФЕРА
000016
404 001642 016602 MOV 24(SP)»R2 ; И СЧЕТЧИК
000024
405 001646 116667 MOVB 22(SP)'BFLAG ;УСТАНОВИТЬ ФЛАГ запиты буфера
000022
176245
406 001654 000170 JMP GXFER(RO) ‘ПЕРЕХОД К ПОДПРОГРАММЕ ВВОДА-ВЫВОДА
000124’
407 и
408 ;*********»i»««*** **********************************;
409 r »
410 • > ПОДПРОГРАММЫ ВВОДА-ВЫВОДА ;
411 • r Г
412 ;**************************************************;
413 >
414 • r OREAD
415 • r
416 ;ЧИТАЕТ ЧИСЛА В ВОСЬМЕРИЧНОМ ФОРМАТЕ
417 •
418 001660 105067 OREAD: CLRB SELECT .‘УСТАНОВИТЬ ФЛАГ ’ВОСЬМЕРИЧНОЕ1
176237
419 001664 000403 BR CREAD ; И ОБОЙТИ
420 • f
421 DREAD
422
423 ?ЧИТАЕТ ЧИСЛА В ДЕСЯТИЧНОМ ФОРМАТЕ
424
425 001666 112767 DREAD: MOVB «1.SELECT ?УСТАНОВИТЬ ФЛАГ ’ДЕСЯТИЧНОЕ’
000001
176227
426 r
427 F ”” ОБЩАЯ ПРОГРАММА ЧТЕНИЯ
428
429 001674 010246 CREAD: MOV R2'-(SP) 'СОХРАНИТЬ СЧЕТЧИК
430 001676 010146 MOV Rl'-(SP) ; И АДРЕС БУФЕРА
431 001700 012701 MOV «BUFFER'Rl •УСТАНОВИТЬ УКАЗАТЕЛЬ
000000’
432 001704 004767 JSR PC.INPSTR 1 И ПОЛУЧИТЬ ЗАПОЛНЕННЫЙ БУФЕР
176232
433 001710 004767 JSR PC'PACK ;УПАКОВАТЬ БУФЕР
176310
434 001714 012601 MOV (SP)+»R1 'ВОССТАНОВИТЬ
435 001716 012602 MOV (SP)+»R2 f РЕГИСТРЫ
436 001720 012703 MOV «BUFFER-1»R3 'УСТАНОВИТЬ ФИКТИВНЫЙ 0ГРАНИЧИТЕЛ1
177777’
437 001724 004767 CRD100: JSR PC.SCAN ;ПОЛУЧИТЬ СЛЕДУЮЩИЙ ОГРАНИЧИТЕЛЬ
176326
438 001730 105767 TSTB SELECT ДОСМОТРЕТЬ. КАКАЯ ПРОГРАММА
176167
439 001734 001403 BEQ CRD120 'ВОСЬМЕРИЧНОЕ -- ПРОПУСТИТЬ
440 001736 004767 JSR PC.D2BIN ;ПРЕОБРАЗОВАТЬ ДЕСЯТИЧНОЕ
176430
441 001742 000402 BR CRD140 ; И ОБОЙТИ
358
442 001744 443 001750 444 001752 445 001754 004767 176330 103410 010521 005302 CRD120: JSR CRD140: BCS CRD200: MOV DEC PC'02BIN CRD900 R5»(R1)+ R2 {ПРЕОБРАЗОВАТЬ ВОСЬМЕРИЧНОЕ (ОШИБКА ПРЕОБРАЗОВАНИЯ {ПОМЕСТИТЬ В БУФЕР ПОЛЬЗОВАТЕЛЯ {УМЕНЬШИТЬ СЧЕТЧИК ПОЛЬЗОВАТЕЛЯ
446 001756 001002 BNE CRD300 {ПЕРЕЙТИ К ВЫПОЛНЕНИИ»
447 001760 000167 JMP RETURN { ИНАЧЕ — ВОЗВРАТ
177622
448 001764 105713 CRD300: TSTB <R3> {КОНЕЦ СТРОКИ?
449 001766 001742 BEQ CREAD {ДА — ЧИТАТЬ ДРУГУЮ СТРОКУ»
450 001770 000755 BR CRD100 ; ИНАЧЕ — ПРОДОЛЖАТЬ
451
452 001772 004767 CRD900:. JSR PC»ERR0R2 {СООБЩИТЬ ОБ ОШИБКЕ
177114
453 001776 005005 CLR R5 {ПОЛУЧИТЬ НОЛЬ
454 002000 000764 BR CRD200 { И ПОМЕСТИТЬ В БУФЕР
455
456 0 PRINT
457 • 7
458 {РАСПЕЧАТЫВАЕТ ЧИСЛА В ВОСЬМЕРИЧНОМ ФОРМАТЕ
459 • >
460 002002 105067 OPRINTs CLRB SELECT {УСТАНОВИТЬ ФЛАГ КАК ’ВОСЬМЕРИЧНОЕ
176115
461 002006 000403 BR CPRINT { И ПЕРЕЙТИ К ОБЩЕЙ ПРОГРАММЕ
462 ?
463 F “ ~ D PRINT
464 f
465 ;распечатывает числа В ДЕСЯТИЧНОМ ФОРМАТЕ
466 f
467 002010 112767 DPRINT: MOVB Я» SELECT {УСТАНОВИТЬ ФЛАГ КАК ’ДЕСЯТИЧНОЕ’
000001
176105
468 r
469 • r ОБЩАЯ ПРОГРАММА РАСПЕЧАТКИ
470 f
471 002016 012704 CPRINT: MOV 09.»R4 •9 ЧИСЕЛ В СТРОКЕ
000011
472 002022 012700 MOV 0BUFFER+6»R0 {УСТАНОВИТЬ УКАЗАТЕЛЬ
000006'
473 002026 012105 CPR050: MOV (RD + .R5 {ПОЛУЧИТЬ ЧИСЛО
474 002030 010246 MOV R2»-(SP) {СОХРАНИТЬ ЭТИ
475 00 2032 010446 MOV R4»-(SP) { РЕГИСТРЫ
476 002034 105767 TSTB SELECT {КАКАЯ ПРОГРАММА?
176063
477 002040 001403 BEQ CPR100 {ВОСЬМЕРИЧНОЕ
478 002042 004767 JSR PC.BIN2D {ПРЕОБРАЗОВАТЬ В ДЕСЯТИЧНОЕ
176620
479 002046 000402 BR CPR200 { И ПРОПУСТИТЬ
480 002050 004767 CPR100: JSR PC.BIN20 {ПРЕОБРАЗОВАТЬ В ВОСЬМЕРИЧНОЕ
176526
481 002054 012604 CPR200: MOV (SP)+»R4 {ВОССТАНОВИТЬ
482 002056 012602 MOV <SP)+»R2 { РЕГИСТРЫ
483 002060 062700 ADD (14.,RO {ВОССТАНОВИТЬ УКАЗАТЕЛЬ
000016
484 002064 005302 DEC R2 {УМЕНЬШИТЬ СЧЕТЧИК
485 002066 001402 BEQ CPR300 {ВЫПОЛНИТЬ»
486 002070 005304 DEC R4 { ИНАЧЕ ВЕРНУТЬСЯ
487 002072 001355 BNE CPR050 { ЗА СЛЕДУЮЩИМ
488 002074 105060 CPR300: CLRB -6(RO) {УСТАНОВИТЬ КОНЕЦ СТРОКИ
177772
489 002100 010246 MOV R2»-(SP) {СОХРАНИТЬ ВАЖНЫЕ
490 002102 010146 MOV R1»-(SP) { РЕГИСТРЫ
491 002104 004767 JSR PC.PRINTX {НАПЕЧАТАТЬ СТРОКУ
176072
492 002110 012601 MOV (SP)+»R1 {ВОССТАНОВИТЬ АДРЕС
493 002112 012602 MOV (SP)+»R2 { И СЧЕТЧИК
494 002114 001340 BNE CPRINT {ЕШЕ ЕСТЬ ЧИСЛА,
495 002116 000167 JMP RETURN { ИНАЧЕ ВЫПОЛНЕНО
177464
496
497 J - - BPRINT ,
498 >
499 (РАСПЕЧАТЫВАЕТ ЧИСЛА В ДВОИЧНОМ ФОРМАТЕ
500
501 00212? 012705 BPRINT: MOV 04»R5 (УСТАНОВИТЬ СЧЕТЧИК ЧИСЕЛ
000004
359
502. 002126 012700 000000’ MOM BUFFER, RO ; И УКАЗАТЕЛЬ
503 002132 012704 BPR050: MOV 416.,R4 •.УСТАНОВИТЬ СЧЕТЧИК БИТОВ
000020
504 002136 012103 MOV (RD+.R3 ;ПОЛУЧИТЬ ЧИСЛО
505 002140 112720 BPR100: MOVB 4’0,(R0) + ;ЗАНЕСТИ КОД НУЛЯ
000060
506 002144 006303 ASL R3 ;СДВИНУТЬ старший бит
507 002146 103002 BCC BPR200 ;ХОРОШО — это ноль,
508 002150 105260 INCB -l(RO) ; ИНАЧЕ УСТАНОВИТЬ КОД ASCII
177777
509 002154 005304 BPR200: DEC R4 ;ВЕРНУТЬСЯ ЗА
510 002156 001370 BNE BPR100 : СЛЕДУЮЩИМ БИТОМ
511 002160 012720 MOV 420040»<R0)+ )ЗАНЕСТИ ДВОЙНОЙ ПРОБЕЛ
020040
512 002164 005302 DEC R2 !УМЕНЬШИТЬ СЧЕТЧИК ЧИСЕЛ
513 002166 001402 BEQ BPR300 ;ВСЕ ОНИ ОБРАБОТАНЫ
514 002170 005305 DEC R5- ;ВЕРНУТЬСЯ ЗА
515 002172 001357 BNE BPR050 ; СЛЕДУЮЩИМ ЧИСЛОМ
516 002174 105010 BPR300: CLRB (RO) ;ЗАНЕСТИ ОГРАНИЧИТЕЛЬ СТРОКИ
517 002176 010246 MOV R2,-(SP) СОХРАНИТЬ ВАЖНЫЕ
518 002200 010146 MOV R1,-(SP) ; РЕГИСТРЫ
519 002202 004767 JSR PC,PRINTX НАПЕЧАТАТЬ СТРОКУ
175774
520 002206 012601 MOV <SP)+»R1 'ВОССТАНОВИТЬ АДРЕС
521 002210 012602 MOV (SP)+,R2 ; И СЧЕТЧИК
522 002212 001343 BNE BPRINT ;ПРОДОЛЖИТЬ,
523 002214 000167 JMP RETURN ; ИНАЧЕ ВЕРНУТЬСЯ
177366
524 r
525 r ** “ A READ
526 f
527 ;ЧИТАЕТ СТРОКУ КОДОВ ASCII
528 r
529 002220 005766 AREAD: TST 22(SP) ;СЧЕТЧИК УКАЗАН?
000022
530 002224 003003 BGT ARD020 ;да,
531 002226 004767 JSR PCiINPSTR ; ИНАЧЕ ПОЛУЧИТЬ СТРОКУ
175710
532 002232 000426 BR APR010 ; и выйти
533
534 002234 010246 ARD020: MOV R2»-(SP) ;СОХРАНИТЬ ИЗМЕНЯЕМЫЕ
’535 002236 010146 MOV Rl.-(SP) ; РЕГИСТРЫ
536 002240 012701 MOV 4BUFFER,R1 ОСТАНОВИТЬ УКАЗАТЕЛЬ БУФЕРА
000000’
537 002244 004767 JSR PC,INPSTR ; И ПОЛУЧИТЬ ЗАПОЛНЕННЫЙ буфер
175672
538 002250 012601 MOV (SP)+,R1 -восстановить
539 002252 012602 MOV (SP)+»R2 ; РЕГИСТРЫ
540 002254 012700 MOV 4BUFFER.R0 ОСТАНОВИТЬ указатель
000000’
541 002260 105710 ARD040: TSTB (RO) ;КОНЕЦ БУФЕРА?
542 002262 001764 BEQ ARD020 ;ДА — ПОЛУЧИТЬ Е«Е РАЗ
543 002264 112021 MOVB (R0)+,(Rl)+ ; ПЕРЕДАТЬ ЗНАК
544 002266 005302 DEC R2 ОМЕНЬШИТЬ СЧЕТЧИК
545 002270 001373 BNE ARD040 'ПЕРЕДАТЬ ЕЩЕ РАЗ,
546 002272 000406 BR APR010 ; ИНАЧЕ ВЫИТИ
547 T
548 A PRINT
549
550 РАСПЕЧАТЫВАЕТ СТРОКУ В КОДЕ ASCII
551
552 002274 005766 APRINTs TST 22(SP) ;СЧЕТЧИК УКАЗАН?
000022
553 002300 003005 BGT APR020 ; да
554 002302 010100 MOV R1,RO ОСТАНОВИТЬ УКАЗАТЕЛЬ БУФЕРА
555 002304 004767 JSR PC,PRINT НАПЕЧАТАТЬ БУФЕР ПОЛЬЗОВАТЕЛЯ
175676
556 002310 000167 APR010: JMP RETURN ; И ВЕРНУТЬСЯ
177272
557
558 002314 012700 APR020: MOV 4BUFFER,R0 ОСТАНОВИТЬ УКАЗАТЕЛЬ
000000’
559 002320 112120 MOVB (Rl)+,(R0)+ ПЕРЕДАТЬ ЗНАК
560 002322 005302 DEC R2 ; И УМЕНЬШИТЬ СЧЕТЧИК
561 002324 001403 BEQ APR100 1СЧЕТЧИК = 0
360
562 002326 020027 000110’ СМР RO.tBUFFER+72. 'БУФЕР ПОЛОН?
563 002332 001372 ВНЕ APR020+4 ;het — продолмить
564 002334 010246 APR100s MOV R2>-(SP) СОХРАНИТЬ ИЗМЕНЯЕМЫЕ
565 002336 010146 MOV Rl.-(SP) ; регистры
566 002340 105010 CLRB (RO) ;вставить нуль
567 002342 004767 JSR POPRINTX ; И ПЕЧАТАТЬ
175634
568 002346 012601 MOV (SP)+,R1 ;восстановить
569 002350 012602 MOV (SP)+»R2 ; регистры
570 002352 001360 BNE APR020 ; СЧЕТЧИК НЕ РАВЕН НУЛИ
571 002354 572 000755 BR APR010 ; ИНАЧЕ ВЕРНУТЬСЯ
573 000001 .END
Рис. Б.2
ПРИЛОЖЕНИЕ В. ТАБЛИЦА КОДА ASCH1
Значение Знак Значение Знак Значение Знак
ООО NUL 053 + 126 V
001 SOH 054 127 W
002 STX 055 — 130 X
003 ЕТХ 056 131 Y
004 EOT 057 / 132 Z
005 ENQ 060 0 133 [
006 АСК 061 1 134 \
007 BEL 062 2 135 J
010 Возврат на шаг 063 3 136 A
011 Горизонтальная табуляция 064 4 137 —
012 Перевод строки 065 5 140 4
013 Вертикальная табуляция 066 6 141 a
014 Перевод формата 067 7 142 b
015 Возврат каретки 070 8 143 c
016 SO 071 9 144 d
017 SI 072 145 e
020 DLE 073 146 f
021 DC1 074 < 147 g
022 DC2 075 = 150 h
023 DC3 076 > 151 i
024 DC4 077 ? 152 j
025 NAK 100 @ 153 k
026 SYN 101 A 154 1
027 ETB 102 В 155 m
030 CAN 103 C 156 n
031 EM 104 D 157 0
032 SUB 105 E 160 P
033 ESC 106 F 161 q
034 FS 107 G 162 r
035 GS 110 H 163 s
036 RS 111 I 164 t
037 US 112 J 165 u
361
Окончание табл.
Значение Знак Значение Знак Значение Знак
040 Пробел 113 К 166 V
041 1 114 L 167 W
042 115 М 170 X
043 # 116 N 171 У
044 $ 117 О 172 Z
045 % 120 Р 173
046 & 121 Q 174 1
047 122 R 175 }
050 ( 123 S 176
051 ) 124 т 177 Стирание
052 * 125 и
1 В нашей стране используется код КОИ-7, в существенной степени совпадающий с
кодом ASCII, но, например, коды строчных латинских букв используются для пропис-
ных букв русского алфавита. — Прим, перев.
ПРИЛОЖЕНИЕ Г. СОГЛАШЕНИЯ, ИСПОЛЬЗУЕМЫЕ В
АССЕМБЛЕРЕ ЭВМ PDP-11
Г. 1. ВВЕДЕНИЕ
• В этом приложении суммируются соглашения, используемые в ассемблере ЭВМ
PDP-11 и имеющие отношение к излагаемому в книге материалу. Те соглашения и воз-
можности ассемблера, которые здесь отсутствуют, можно найти в соответствующем ру-
ководстве по ассемблеру. В этом приложении ссылки на текст книги заключены в квад-
ратные скобки, например [8.3] отсылает к разд. 8.3.
Г.2. НАБОР ЗНАКОВ ЭВМ PDP-11
Ассемблер ЭВМ PDP-11 распознает знаки, которые перечислены в приложении В. Это
в существенной степени те знаки, которые можно найти в любом ASCII-кодированном
терминале ЭВМ. Специальная интерпретация ассемблера некоторых знаков дается в
следующем разделе.
Г.З. СПЕЦИАЛЬНЫЕ ЗНАКИ
Знаки из общего набора, которые имеют специальные значения для ассемблера ЭВМ
PDP-11, сведены в следующую таблицу
Символ
Интерпретация ассемблером
(запятая) символ разделения
(двоеточие) терминатор символьной метки [6.4]
(двойное двоеточие) терминатор символьной метки, определяющий символ
как глобальный [9.4]
(знак равно) прямое присваивание значения символу [11.2]
362
Окончание табл.
Символ Интерпретация ассемблером
# (знак номера) символ непосредственного выражения [б.З]
@ символ косвенной адресации [7.5]
У (точка с запятой) символ начала комментария [6.7]
<. . . . > (угловые скобки) группирующие символы [13.5]
+ (знак плюс) символ сложения или символ последующего автоувеличе- ния [7.5]
— (знак минус) символ вычитания или символ предшествующего автоумень- шения [7.5]
* (звездочка) символ умножения
/ (косая черта) символ деления
(апостроф) символ в коде ASCII [10.7] или символ конкатенации [13.6]
t ИЛИ А (верхняя стрелка) символ управления временным основанием системы счисления [Г.б]
Г.4. ФОРМАТ ИСХОДНОГО ПРЕДЛОЖЕНИЯ
Каждая строка исходного кода, вводимая в ассемблер, должна иметь следующий
вид1:
метка-, оператор операнду операнду ; комментарий
или
директива
Все компоненты исходной строки являются необязательными, за исключением того,
что если присутствует операнд, то должна быть включена мнемоника оператора или ди-
рективы. Если этого не сделано, то ассемблер будет пытаться использовать по умолча-
нию директиву .WORD.
Метка. Если в строке исходного кода присутствует метка, то она должна быть пер-
вым элементом строки, а вслед за ней должно стоять двоеточие (или двойное двоето-
чие) . На одной строке может размещаться больше чем одна метка, например:
LABL1: LABL2: LABL3: LABL4:
в этом случае всем меткам присваивается одно и то же значение. Допустимое постро-
ение имен символьных меток см. в разд. Г.5.
Оператор. Если включен оператор, то он должен иметь вид мнемоники инструкции.
Допустимые мнемоники инструкций даны в приложении А.
Директива. Допустимые директивы ассемблера даны в разд. Г.8 данного приложе-
ния.
Операнды. Один оператор ЭВМ PDP-11 может принимать 0, 1 или 2 операнда. Ди-
ректива может включать 0 или 1 операнд (более точно, аргументов).
Комментарий. Любая часть строки (включая и полную строку), которой предшеству-
ет точка с запятой, берется как комментарий и игнорируется ассемблером, за исклю-
чением того, что она воспроизводится в листинге программы.
1 Строки исходного кода, вызывающие макроинструкции, являются исключением
(см. гл. 13).
363
Г.5. СИМВОЛЫ
Символы бывают двух типов: постоянные и определенные пользователем. Постоян-
ные символы — это мнемоники инструкций и директивы ассемблера ЭВМ PDP-11. Все
другие символы являются определенными пользователем.
В символе допускается использовать следующие знаки:
А - Z (знаки букв)
0-9 (знаки цифр)
$ (знак доллара)
(точка)
Символ состоит из одного или нескольких таких знаков, причем первый знак не должен
быть цифрой1. Значащими для ассемблера являются только первые шесть знаков, так
что символы CONTINUATION и CONTINUE будут восприниматься как идентичные, а
именно CONTIN.
Хотя имя символа может начинаться с точки, оно не должно состоять только из точ-
ки (см. разд. Г.7).
За исключением имен макроинструкций (см. гл. 13) определенный пользователем
символ получает значение при его появлении либо в качестве метки [6.4], когда ему
присваивается текущее значение счетчика распределения ячеек ассемблера, либо в левой
части оператора прямого присваивания [11.2]. С помощью прямого присваивания (=)
одному символу в одной и той же программе может присваиваться значение несколько
раз, причем всякий раз его значением будет последнее из присвоенных. В качестве мет-
ки символ не может появляться больше одного раза, а также не должно быть так, что
символу придается значение с помощью прямого присваивания и он появляется в каче-
стве метки.
Г.6. ЧИСЛА
Все числа, обрабатываемые ассемблером, полагаются восьмеричными (по основанию
8) со следующими исключениями:
Числа, за которыми следует десятичная точка (такие, как 29.), воспринимаются как
десятичные (по основанию 10) [8.8].
Основание системы счисления может изменяться с помощью директивы .RADIX [Г.8].
Числа, непосредственно следующие за конструкцией с верхней стрелкой, интерпретиру-
ются со следующим основанием:
t Ви (и воспринимается как двоичное представление)
t Ои (и воспринимается как восьмеричное представление)
tDn (и воспринимается как десятичное представление)
Г.7. СЧЕТЧИК РАСПРЕДЕЛЕНИЯ ЯЧЕЕК
В процессе ассемблирования ассемблер ведет счетчик распределения ячеек, с помо-
щью которого он следит за адресами, в которых ассемблируются инструкции. На этот
счетчик можно ссылаться во время ассемблирования с помощью символа точки. Таким
образом, например, оператор прямого присваивания
BLOCK: . = . + п
полностью эквивалентен следующему:
BLOCK: .BLKB п
1 Исключением являются локальные символы, которые должны состоять исключи-
тельно из знаков цифр и заканчиваться знаком доллара (см. разд. 13.7).
364
Счетчик распределения ячеек следит за значением адреса первого слова ассемблиру-
емой инструкции даже для инструкций, занимающих несколько слов. Таким образом,
в процессе ассемблирования, например, инструкции MOV R0, . + 14 значением счетчика
распределения ячеек будет адрес оператора MOV. В этом отношении счетчик распределе-
ния ячеек не вполне отражает значение программного счетчика PC во время выполнения
программы.
Хотя конструкции наподобие BNE .+4, используемые для обхода одного слова в слу-
чае, если бит Z в PSW сброшен, допускаются, они должны использоваться с особым вни-
манием, поскольку последующие модификации программы могут привести к измене-
нию желаемого смещения (может потребоваться, например, изменить . + 4 на . + 6), но
программист может не заметить такое изменение, в результате чего появится ошибка,
которую не так легко найти.
Г.8. ДИРЕКТИВЫ АССЕМБЛЕРА
Ниже приводятся некоторые из наиболее полезных директив ассемблера и их аргу-
менты, а также указываются действия, которые предпринимаются ассемблером, когда
встречается данная директива.
Директива Аргумент (ы) Операция
.ASCII Строка Генерирует блок байтов, состоящий из кода ASCII знаков строки (которая предполагается заключен- ной в разграничительные знаки) [10.5]
.ASCIZ Строка То же, что и при .ASCII, за исключением того, что знак NUL (код ASCII ООО) добавляется в конце сгенерированной строки [10.5]
BLKB Выражение Резервирует блок памяти длиной п байт, где п равно значению выражения [10.4]
.BLKW Выражение Резервирует блок памяти длиной п слов, где п равно значению выражения [6.7]
.BYTE Выр;, Выр, ,... Генерирует последовательные байты памяти, со- держащие значения: выражение,, выражение,, . .. [10.4]
.END Пусто Обозначает физический конец исходной програм- мы
Адрес Обозначает физический коней исходной програм- мы с указанием стартового адреса для запуска вы- полнения программы [6.7]
.ENDC Пусто Обозначает конец условного блока [13.11]
.ENDM . Пусто Обозначает конец определения макроинсгрукции [13.3], блока повторения с неопределенным чис- лом повторений [13.9] или блока повторения [13-9]
.EVEN Пусто Гарантирует, что счетчик распределения ячеек ас- семблера будет содержать четное число, путем при- бавления единицы, если необходимо [10.4]
.GLOBL Сим,, Сим,,. .. 1 Определяет символ,, символ,,. .. как глобальные [9-2]
IF Условие, Аргумент (ы) Начинает условный блок [13.11]
365
Продолжение
Директива Аргумент (ы) Операция
.IFF Пусто Начинает блок подусловия ’’если ложно”. Появля- ется только внутри условного блока [13.13]
.IFT Пусто Начинается блок подусловия ’’если истинно”. По- является только внутри условного блока [13.13]
.IFTF Пусто Начинает безусловно ассемблируемый блок. Появ- ляется только внутри условного блока [13.13]
.IRP Символ, < аргументы) Начинает блок повторения с неопределенным чис- лом повторений; символ последовательно прини- мает значения аргументов из списка аргументов [13-9]
.IRPC Символ, строка Начинает блок повторения с неопределенным чис- лом повторений; символ последовательно прини- мает значения знаков из строки [13.9]
.LIST Пусто CND СОМ ME SRC Вызывает распечатку исходной программы, ком- ментариев, объектного кода и таблицы символов (используется ассемблером по умолчанию) Распечатывает исходный блок для инструкций не- удовлетворенного условия в условном блоке (ис- пользуется ассемблером по умолчанию) Распечатывает комментарии исходной программы (используется ассемблером по умолчанию) Распечатывает код, сгенерированный при расшире- нии макроинструкций [13.3] Распечатывает исходную программу (используется ассемблером по умолчанию)
SYM Распечатывает таблицу символов (используется ас- семблером по умолчанию)
.MACRO Символ, аргумент(ы) Начинает определение макроинструкции [13.3 ]
.MEXIT Пусто Приводит к выходу из текущего расширения ма- кроинструкции [13.12]
.NLIST Пусто CND СОМ ME SRC SYM Запрещает генерацию листинга исходной програм- мы Подавляет листинг неудовлетворенных условий в условном блоке [13.14] Подавляет листинг комментариев исходной про- граммы Подавляет листинг макрорасширений (использует- ся ассемблером по умолчанию) Подавляет листинг исходного кода Подавляет листинг таблицы символов
.ODD Пусто Гарантирует, что счетчик распределения ячеек ас- семблера будет содержать нечетное число путем прибавления 1, если необходимо [10.4]
.RADIX Пусто и То же самое, что .RADIX 8 Изменяет основание системы счисления ассембле- ра нал, где п = 2, 4,8 или 10 [Г.б]
.REPT Выражение Начинает блок повторения, который генерирует п копий следующего за ним кода вплоть до следую- щей директивы .ENDM, где п равно значению вы- ражения [13.9]
Окончание
Директива • Аргумент (ы) Операция
.TITLE Строка Приводит к тому, что заданная строка появляется в верхней части каждой страницы листинга про- граммы [6.7]
.WORD Выр,, Выр2, .. . Генерирует последовательные слова памяти, содер- жащие значения: выражение,, выражение2, . . . [6-7]
Г.9. ОШИБКИ ВО ВРЕМЯ АССЕМБЛИРОВАНИЯ
Ошибки, обнаруженные ассемблером во время первого или второго прохода или в
процессе обоих проходов, помечаются флагами ошибок с левой стороны листинга про-
граммы. Если на одной строке исходного кода обнаруживается больше одной ошибки,
то распечатываются несколько флагов ошибок. Флаг ошибки вместе с номером, содер-
жащей ошибку строки исходного кода, будет распечатываться даже и в том случае, если
листинг исходного кода в настоящее время подавлен (например, с помощью .NL1ST или
.NLIST SRC).
Коды ошибок и пояснение их смысла приведены ниже.
Код ошибки Ошибка
А Ошибка ассемблирования. Этот код ошибки генерируется в результате целого ряда ошибок, которые могут быть разделены на две основные ка- тегории: 1. Директивы. Директиве ассемблера передается недопустимый аргумент или отсутствует аргумент, когда он должен передаваться. Эта ошибка будет генерироваться также при отсутствии завершающего раз- граничителя в директивах .ASCII или .ASCIZ 2. Адресация. Смещение в инструкции ветвления выходит из диапазона от —1281О до +12710 слов. Исходное предложение содержит недопустимую адресную спецификацию (например, .BLKW TWENTY, где TWENTY - это чувствительный к перемещению символ). Недопусти- мое использование счетчика распределения ячеек ассемблера
В Граничная ошибка. Счетчик распределения ячеек ассемблера содержал нечетное значение, когда ассемблировались инструкция или данные по- словного типа
D Дважды определенный символ. Сделана ссылка на символ, которому бы- ло присвоено больше одного значения (многократно определенный символ)
Е М Ассемблер не обнаружил директиву .END Многократно определенный символ. Символ был определен больше од- ного раза или как метка, или как метка и в операторе прямого присва- ивания
N Числовая ошибка. Один или несколько знаков в числовой строке недо- пустимы при текущем основании системы счисления
О Ошибка операционного кода. Мнемоника оператора или директивы вы- падает из контекста
367
Окончание табл.
Код ошибки Ошибка
р Фазовая ошибка- Значение, присвоенное метке на первом проходе ас- семблирования, не согласуется с его значением на втором проходе
Q Сомнительный синтаксис. Отсутствует один или несколько операндов или аргументов, либо ассемблер завершил сканирование исходной стро- ки до того, как встретил признак конца строки
R Регистровая ошибка. Неправильно использовался регистр или на него бы- ла неправильная ссылка
Т Ошибка усечения. Число слишком большое, чтобы могло поместиться в заданном 16-битовом слове или восьмибитовом байте
и Неопределенный символ. Была сделана ссылка на символ, который в программе никогда не определялся (не появлялся ни как метка, ни в операторе прямого присваивания) и не появлялся в директиве .GLOBL
Z Предостережение о несовместимости. Ассемблированная инструкция мо- жет неодинаково выполняться на различных процессорах семейства ЭВМ PDP-11
СПИСОК ЛИТЕРАТУРЫ
Соучек Б. Мини-ЭВМ в системах обработки информации: Пер. с англ. — М.: Мир, 1976.
Малые ЭВМ и их применение/ Под ред. Наумова Б. Н. — М.: Статистика, 1980.
Вигдорчик Г. В., Воробьев А. Ю., Праченко В. Д. Основы программирования на Ассемб-
лере для СМ ЭВМ. — М.: Финансы и статистика, 1983.
Экхауз Р., Моррис Л. Мини-ЭВМ: Организация и программирование: Пер. с англ. - М.:
Финансы и статистика, 1983.
Сингер М. Мини-ЭВМ PDP-11. Программирование на языке ассемблера и организация ма-
шины: Пер. с англ. - М.: Мир, 1984.
О ГЛАВ ПЕНИЕ
Предисловие-переводчика............................................. - - - 5
Предисловие............................................................. &
Глава 1. Основы вычислительной техники..............................10
1.1. Введение................................................ Ю
1.2. Конфигурация минимальной вычислительной системы...........И
1.3. Центральный процессор....................................12
1.4. Оперативная память.......................................12
1.5. Внешние устройства.......................................12
1.6. Шины.....................................................12
Глава 2. Оперативная память и архитектура ЭВМ.......................13
2.1. Информация...............................................13
2.2. ’Хранениеинформации......................................14
2.3. Запоминающие устройства ЭВМ..............................15
2.4. Элементы памяти..........................................17
2.5. Представление чисел......................................18
2.6. Изменение нотации.................................... 21
2.7. Машинные слова и системы счисления......... . . .......22
2.8. Дополнение до единицы и дополнение до двух ..............26
2.9. Индикаторы переноса и переполнения.......................27
2.10. Еще немного о четырехбитовой арифметике.................28
2.11. Упражнения............................................. 31
Глава 3. Логика........................................................33
3.1. Высказывания и союзы.....................................33
3.2. Переключательные схемы...................................37
3.3. Еще раз б высказываниях с изменением нотации.............39
3.4. Реле и вентили.......................................... 39
3.5. Две арифметические схемы........................ . . ,...43
3.6. Упражнения.................................... . . ..... 44
Глава 4. 16-битовые слова и адресация памяти......... . . . . . .... 47
.4.1 . Вычислительные машины с 16-битовым словом . . ; ;......47
4.2. Числовые представления................................. 48
4.3. Полуслова (байты)........................................49
4.4. Адресация памяти.........................................51
4.5. Упражнения...............................................53
Глава 5. Хранимые инструкции и центральный процессор...................55
5.1. Концепция хранимых инструкций...........................55
5.2. Регистры центрального процессора........................58
369
5.3. Программный счетчик (PC) и извлечение инструкций........ 59
5.4. Цикл выполнения инструкции.............................. 62
5.5. Загрузка оперативной памяти............................. 63
5.6. Слово состояния процессора (PSW)........................ 66
5.7. Инструкция условного перехода........................... 67
5.8. Две полных (хотя и неинтересных) программы.............. 72
5.9. Некоторые другие регистры ЦП............................ 77
5.10. Использование регистров общего назначения............... 78
5.11. Упражнения............................................. 80
Глава 6. Ассемблирование (построение) программы...................... 81
6.1. Создание программы...................................... 81
6.2. Образец программы....................................... 81
6.3. Мнемонический язык...................................... 90
6.4. Образец программы, переписанный в мнемоническом виде.... 93
6.5. Ассемблирование мнемонической программы................. 96
6.6. Ассемблер ЭВМ PDP-11....................................100
6.7. Модификация образца программы...........................101
6.8. Упражнения..............................................105
Глава 7. Форматы операторов и режимы адресации...................... 107
7.1. Операторы и инструкции..................................107
7.2. Типы инструкций и форматы операторов....................108
7.3. Условные ветвления......................................109
7.4. Одно-и друхоперандные инструкции....... . . . ........109
7.5. Режимы адресации . .....................................110
7.6. Специальный случай *- режимы адресации с регистром 7
(программным счетчиком).....................................120
7.7. Некоторые исключительные случаи.........................131
7.8. Набор инструкций ЭВМ PDP-11.............................134
7.9. Несколько заключительных замечаний......................135
7.10. Упражнения.............................................136
Глава 8. Стеки и подпрограммы........................................142
8.1. Структуры данных........................................142
8.2. Реализация стека в оперативной памяти...................145
8.3. Некоторые потенциальные проблемы при ведении стека......148
8.4. Специальный указатель стека SP (R6) и аппаратный стек...151
8.5. Подпрограммы............................................153
8.6. Инструкции JSR и RTS....................................157
8.7. Подпрограммы, обращающиеся к другим подпрограммам.......159
8.8. Передача аргументов подпрограмме.................... 162
8.9. Использование PC в качестве регистра для перехода к под-
программе.......................................................
8.10. Рекурсивные подпрограммы...............................170
8.11. Подпрограммы с несколькими точками входа...............172
8.12. Упражнения.............................................174
Глава 9. Внешние символы, перемещение и компоновка...................182
9.1. Внешние подпрограммы....................................182
9.2. Внешние символы.........................................184
9.3. Операционная система....................................186
9.4. Объектные файлы и внешние символы.......................187
9.5. Перемещение объектного модуля...........................190
370
9.6. Добавление объектных модулей...........................-191
9.7. Загрузимк-редактор-компоновщик как служебная программа
операционной системы....................................194
9.8. Выполнение загрузочного модуля......................... 197
9.9. Позиционно-независимый код..............................197
9.10. Несколько заключительных замечаний......................199
9.11. Упражнения..............................................199
Глава 10. Знаковые коды...............................................202
10.1. Обработка знаков........................................202
10.2. Преобразование знаков . ........................'.......202
10.3. Код ASCII...............................................203
10.4. Директивы .BYTE, .BLKB и .EVEN..........................204
10.5. Директивы .ASCII и .ASCIZ...............................207
10.6. Считывание и распечатка текста в коде ASCII.............208
10.7. Конструкция с апострофом............................. 210
10.8. Упражнения..............................................210
Глава И. Инструкция TRAP...........................................214
11.1. Еще раз о подпрограммах.................................214
11.2. Гипотетическая инструкция JTZ...........................215
11.3. Инструкция TRAP.........................................218
11.4. Переход с помощью инструкции TRAP к нескольким точкам входа220
11.5. Альтернативный набор регистров общего назначения........222
11.6. Еще раз о компоновщике..................................224
11.7. Упражнения..............................................225
Глава 12. Прерывания и обработка ввода-вывода......................229
12.1. Введение................................................229
12.2. Прерывания..............................................229
12.3. Процессорные прерывания.................................234
12.4. Еще раз об адресации памяти.............................241
12.5. Режим работы без прерываний.............................242
12.6. Дальнейшие подробности последовательности прерывания....245
12.7. Приоритеты устройств....................................248
12.8. Приоритет центрального процессора.................. ... 253
12.9. Консольный терминал................................... 260
12.10. Упражнения........................................... 267
Глава 13. Макроинструкции и условное ассемблирование................ 270
13.1. Введение................................................270
13.2. Макроинструкции...................................... . 271
13.3. Ассемблер микроинструкций ЭВМ PDP-11....................273
13.4. Несколько слов в качестве предостережения...............274
13.5. Передача аргументов макроинструкции.....................277
13.6. Передача аргументов макроинструкции с помощью конкатенации 283
13.7. Локальные символы . ....................................286
13.8. Автоматически генерируемые локальные символы............288
13.9. Блоки повторения........................................292
13.10. Макроинструкция для сдвига битов.......................297
13.11. Условно ассемблируемые инструкции......................298
371
13.12. Директива ассемблера .MEXIT.......................... 304
13.13. Подусловия.............................................307
13.14. Некоторые заключительные замечания.....................312
13.15. Упражнения............................................ 313
Приложение А. Набор инструкций ЭВМ PDP-11.............................320
АЛ. Символы и сокращения......................................320
А.2. Набор инструкций в алфавитном порядке мнемоник...........321
А.З. Набор инструкций в числовом порядке операционного кода...346
Приложение Б. Макроинструкции ввода-вывода............................347
Б.1. Введение.................................................347
Б.2. Вызов макроинструкций ввода-вывода.......................347
Б.З. Обнаружение ошибок.......................................348
Б.4. Формат ввода........ . . ...............................349
Б.5. Формат вывода............................................349
Б.6. Специальный случай — макроинструкции $IN.ASCh SOUT.ASC . . 350
Б.7. Определения макроинструкций ввода-вывода.................350
Б.8. Подпрограмма ввода-вывода ”$ .. 351
Приложение В. Таблица кода ASCII......................................361
Приложение Г. Соглашения, используемые в ассемблере ЭВМ PDP-11........362
Г.1. Введение.................................................362
Г.2. Набор знаков ЭВМ PDP-11..................................362
Г.З. Специальные знаки........................................362
Г.4. Формат исходного предложения.............................363
Г.5. Символы..................................................364
Г.6. Числа....................................................364
Г.7. Счетчик распределения ячеек..............................364
Г.8. Директивы ассемблера.....................................365
Г.9. Ошибки во время ассемблирования..........................367
Список литературы.....................................................368
Фрэнк Т. С.
Ф93 PDP-11: Архитектура и программирование: Пер. с англ. — М.:
Радио и связь, 1986. — 376 с.: ил.
В книге американского автора рассмотрены как общие вопросы про-
граммирования (организация ЭВМ семейства PDP-11, представление чи-
сел, системы счисления), так и методы программирования на языке ас-
семблера для ЭВМ данного семейства. Приведено подробное и достаточно
полное описание машинных команд.
Большими достоинствами книги являются независимость излагаемого
материала от используемой операционной системы и включение редко рас-
сматриваемых вопросов ассемблирования, перемещения и связывания
программ.
Для программистов, работающих на ЭВМ типа PDP-11.
2405000000 - 204
ф-----------------149 _ 86
046(01) - 86
ББК 32.973